Merge pull request #191 from docker/feat-vsa-input-attestations

feat: vsa input attestations
This commit is contained in:
Joel Kamp
2024-10-08 08:30:06 -05:00
committed by GitHub
14 changed files with 79 additions and 39 deletions

View File

@@ -316,8 +316,8 @@ func buildImageFromLayers(layers []*Layer, manifest *v1.Descriptor, subject *v1.
return newImg, nil
}
func ExtractEnvelopes(manifest *Manifest, predicateType string) ([]*Envelope, error) {
var envs []*Envelope
func ExtractEnvelopes(manifest *Manifest, predicateType string) ([]*EnvelopeReference, error) {
var envs []*EnvelopeReference
dsseMediaType, err := DSSEMediaType(predicateType)
if err != nil {
return nil, fmt.Errorf("failed to get DSSE media type for predicate '%s': %w", predicateType, err)
@@ -333,11 +333,20 @@ func ExtractEnvelopes(manifest *Manifest, predicateType string) ([]*Envelope, er
return nil, fmt.Errorf("failed to get layer contents: %w", err)
}
defer reader.Close()
env := new(Envelope)
env := new(EnvelopeReference)
err = json.NewDecoder(reader).Decode(&env)
if err != nil {
return nil, fmt.Errorf("failed to decode envelope: %w", err)
}
var uri string
if len(manifest.OriginalDescriptor.URLs) > 0 {
uri = manifest.OriginalDescriptor.URLs[0]
}
env.ResourceDescriptor = &ResourceDescriptor{
MediaType: string(mt),
Digest: map[string]string{manifest.OriginalDescriptor.Digest.Algorithm: manifest.OriginalDescriptor.Digest.Hex},
URI: uri,
}
envs = append(envs, env)
}
}

View File

@@ -45,8 +45,8 @@ func (r *LayoutResolver) fetchManifest() (*Manifest, error) {
return r.Manifest, nil
}
func (r *LayoutResolver) Attestations(_ context.Context, predicateType string) ([]*Envelope, error) {
var envs []*Envelope
func (r *LayoutResolver) Attestations(_ context.Context, predicateType string) ([]*EnvelopeReference, error) {
var envs []*EnvelopeReference
dsseMediaType, err := DSSEMediaType(predicateType)
if err != nil {
return nil, fmt.Errorf("failed to get DSSE media type for predicate '%s': %w", predicateType, err)
@@ -60,17 +60,26 @@ func (r *LayoutResolver) Attestations(_ context.Context, predicateType string) (
if mts != dsseMediaType {
continue
}
env := new(Envelope)
env := new(EnvelopeReference)
// parse layer blob as json
r, err := attestationLayer.Layer.Uncompressed()
layer, err := attestationLayer.Layer.Uncompressed()
if err != nil {
return nil, fmt.Errorf("failed to get layer contents: %w", err)
}
defer r.Close()
err = json.NewDecoder(r).Decode(env)
defer layer.Close()
err = json.NewDecoder(layer).Decode(env)
if err != nil {
return nil, fmt.Errorf("failed to decode envelope: %w", err)
}
var uri string
if len(r.Manifest.OriginalDescriptor.URLs) > 0 {
uri = r.Manifest.OriginalDescriptor.URLs[0]
}
env.ResourceDescriptor = &ResourceDescriptor{
MediaType: string(mt),
Digest: map[string]string{r.Manifest.OriginalDescriptor.Digest.Algorithm: r.Manifest.OriginalDescriptor.Digest.Hex},
URI: uri,
}
envs = append(envs, env)
}
return envs, nil

View File

@@ -12,14 +12,14 @@ import (
var _ oci.ImageDetailsResolver = MockResolver{}
type MockResolver struct {
Envs []*Envelope
Envs []*EnvelopeReference
Image string
PlatformFn func() (*v1.Platform, error)
DescriptorFn func() (*v1.Descriptor, error)
ImangeNameFn func() (string, error)
}
func (r MockResolver) Attestations(_ context.Context, _ string) ([]*Envelope, error) {
func (r MockResolver) Attestations(_ context.Context, _ string) ([]*EnvelopeReference, error) {
return r.Envs, nil
}

View File

@@ -109,12 +109,12 @@ func (r *ReferrersResolver) resolveAttestations(ctx context.Context, predicateTy
return aManifests, nil
}
func (r *ReferrersResolver) Attestations(ctx context.Context, predicateType string) ([]*Envelope, error) {
func (r *ReferrersResolver) Attestations(ctx context.Context, predicateType string) ([]*EnvelopeReference, error) {
manifests, err := r.resolveAttestations(ctx, predicateType)
if err != nil {
return nil, fmt.Errorf("failed to resolve attestations: %w", err)
}
var envs []*Envelope
var envs []*EnvelopeReference
for _, attest := range manifests {
es, err := ExtractEnvelopes(attest, predicateType)
if err != nil {

View File

@@ -24,7 +24,7 @@ func NewRegistryResolver(src *oci.RegistryImageDetailsResolver) (*RegistryResolv
}, nil
}
func (r *RegistryResolver) Attestations(ctx context.Context, predicateType string) ([]*Envelope, error) {
func (r *RegistryResolver) Attestations(ctx context.Context, predicateType string) ([]*EnvelopeReference, error) {
if r.Manifest == nil {
attest, err := FetchManifest(ctx, r.Identifier, r.ImageSpec.Platform)
if err != nil {

View File

@@ -8,5 +8,5 @@ import (
type Resolver interface {
oci.ImageDetailsResolver
Attestations(ctx context.Context, mediaType string) ([]*Envelope, error)
Attestations(ctx context.Context, mediaType string) ([]*EnvelopeReference, error)
}

View File

@@ -67,6 +67,17 @@ type Extension struct {
Ext *DockerDSSEExtension `json:"ext"`
}
type EnvelopeReference struct {
*Envelope
ResourceDescriptor *ResourceDescriptor `json:"resourceDescriptor"`
}
type ResourceDescriptor struct {
MediaType string `json:"mediaType"`
Digest map[string]string `json:"digest"`
URI string `json:"uri,omitempty"`
}
type AnnotatedStatement struct {
OCIDescriptor *v1.Descriptor
InTotoStatement *intoto.Statement

View File

@@ -12,13 +12,13 @@ const (
)
type VSAPredicate struct {
Verifier VSAVerifier `json:"verifier"`
TimeVerified string `json:"timeVerified"`
ResourceURI string `json:"resourceUri"`
Policy VSAPolicy `json:"policy"`
InputAttestations []VSAInputAttestation `json:"inputAttestations,omitempty"`
VerificationResult string `json:"verificationResult"`
VerifiedLevels []string `json:"verifiedLevels"`
Verifier VSAVerifier `json:"verifier"`
TimeVerified string `json:"timeVerified"`
ResourceURI string `json:"resourceUri"`
Policy VSAPolicy `json:"policy"`
InputAttestations []ResourceDescriptor `json:"inputAttestations,omitempty"`
VerificationResult string `json:"verificationResult"`
VerifiedLevels []string `json:"verifiedLevels"`
}
type VSAVerifier struct {
@@ -31,11 +31,6 @@ type VSAPolicy struct {
DownloadLocation string `json:"downloadLocation,omitempty"`
}
type VSAInputAttestation struct {
Digest map[string]string `json:"digest"`
MediaType string `json:"mediaType"`
}
func ToVSAResourceURI(sub intoto.Subject) (string, error) {
// parse purl
purl, err := packageurl.FromString(sub.Name)

View File

@@ -20,13 +20,13 @@ import (
"github.com/stretchr/testify/require"
)
func loadAttestation(t *testing.T, path string) *attestation.Envelope {
func loadAttestation(t *testing.T, path string) *attestation.EnvelopeReference {
ex, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
env := new(attestation.Envelope)
env := new(attestation.EnvelopeReference)
err = json.Unmarshal(ex, env)
if err != nil {
t.Fatal(err)
@@ -44,7 +44,7 @@ func TestRegoEvaluator_Evaluate(t *testing.T) {
require.NoError(t, err)
re := policy.NewRegoEvaluator(true, verifier)
defaultResolver := attestation.MockResolver{
Envs: []*attestation.Envelope{loadAttestation(t, ExampleAttestation)},
Envs: []*attestation.EnvelopeReference{loadAttestation(t, ExampleAttestation)},
}
defaultPlatform, err := v1.ParsePlatform("linux/amd64")
require.NoError(t, err)
@@ -122,7 +122,7 @@ func TestLoadingMappings(t *testing.T) {
func TestCreateAttestationResolver(t *testing.T) {
mockResolver := attestation.MockResolver{
Envs: []*attestation.Envelope{},
Envs: []*attestation.EnvelopeReference{},
}
layoutResolver := &attestation.LayoutResolver{}
registryResolver := &oci.RegistryImageDetailsResolver{}

View File

@@ -83,7 +83,7 @@ func (r *NullAttestationResolver) ImageDescriptor(_ context.Context) (*v1.Descri
return nil, nil
}
func (r *NullAttestationResolver) Attestations(_ context.Context, _ string) ([]*attestation.Envelope, error) {
func (r *NullAttestationResolver) Attestations(_ context.Context, _ string) ([]*attestation.EnvelopeReference, error) {
r.called = true
return nil, nil
}

View File

@@ -8,10 +8,11 @@ import (
)
type Summary struct {
Subjects []intoto.Subject `json:"subjects"`
SLSALevels []string `json:"slsa_levels"`
Verifier string `json:"verifier"`
PolicyURI string `json:"policy_uri"`
Subjects []intoto.Subject `json:"subjects"`
Inputs []attestation.ResourceDescriptor `json:"input_attestations"`
SLSALevels []string `json:"slsa_levels"`
Verifier string `json:"verifier"`
PolicyURI string `json:"policy_uri"`
}
type Violation struct {

View File

@@ -37,11 +37,17 @@ subjects contains subject if {
some subject in statement.subject
}
inputs contains desc if {
some att in atts
desc := att.resourceDescriptor
}
result := {
"success": true,
"violations": set(),
"summary": {
"subjects": subjects,
"input_attestations": inputs,
"slsa_levels": ["SLSA_BUILD_LEVEL_3"],
"verifier": "docker-official-images",
"policy_uri": "https://docker.com/official/policy/v0.1",

View File

@@ -189,6 +189,7 @@ func toVerificationResult(p *policy.Policy, input *policy.Input, result *policy.
Policy: vsaPolicy,
VerificationResult: outcomeStr,
VerifiedLevels: result.Summary.SLSALevels,
InputAttestations: result.Summary.Inputs,
},
},
}, nil

View File

@@ -37,11 +37,11 @@ func TestVerifyAttestations(t *testing.T) {
ex, err := os.ReadFile(ExampleAttestation)
assert.NoError(t, err)
env := new(attestation.Envelope)
env := new(attestation.EnvelopeReference)
err = json.Unmarshal(ex, env)
assert.NoError(t, err)
resolver := &attestation.MockResolver{
Envs: []*attestation.Envelope{env},
Envs: []*attestation.EnvelopeReference{env},
}
testCases := []struct {
@@ -124,7 +124,15 @@ func TestVSA(t *testing.T) {
assert.Equal(t, PassPolicyDir+"/policy.rego", attestationPredicate.Policy.DownloadLocation)
assert.Equal(t, "https://docker.com/official/policy/v0.1", attestationPredicate.Policy.URI)
// this is the digest of the policy file
assert.Equal(t, map[string]string{"sha256": "ae71defe3b9ecebdf4f939a396b68884d0cba3c2c9d78ce5e64146d9487b0ade"}, attestationPredicate.Policy.Digest)
assert.Equal(t, map[string]string{"sha256": "fe1d4973f3521009a3adec206946e12aae935a2aceeb1e01f52b5d4cb9de79a5"}, attestationPredicate.Policy.Digest)
assert.Greater(t, len(attestationPredicate.InputAttestations), 0)
for _, input := range attestationPredicate.InputAttestations {
require.NotEmpty(t, input.Digest)
digest, ok := input.Digest["sha256"]
assert.True(t, ok)
assert.NotEmpty(t, digest)
assert.Contains(t, []string{"application/vnd.in-toto.provenance+dsse", "application/vnd.in-toto.spdx+dsse"}, input.MediaType)
}
}
func TestVerificationFailure(t *testing.T) {