Skip to content
This repository was archived by the owner on Dec 3, 2024. It is now read-only.

Commit 357768d

Browse files
authored
Various fixes (#63)
* Fix digest resolution and attestation style * Add a bunch more tests * Rename fields for consistency * Remove copy-pasta * Value -> pointer
1 parent 6bd57e0 commit 357768d

File tree

8 files changed

+244
-95
lines changed

8 files changed

+244
-95
lines changed

pkg/attest/verify.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@ func Verify(ctx context.Context, src *oci.ImageSpec, opts *policy.PolicyOptions)
1818
if err != nil {
1919
return nil, fmt.Errorf("failed to create image details resolver: %w", err)
2020
}
21-
21+
if opts.AttestationStyle == "" {
22+
opts.AttestationStyle = config.AttestationStyleReferrers
23+
}
24+
if opts.ReferrersRepo != "" && opts.AttestationStyle != config.AttestationStyleReferrers {
25+
return nil, fmt.Errorf("referrers repo specified but attestation source not set to referrers")
26+
}
2227
pctx, err := policy.ResolvePolicy(ctx, detailsResolver, opts)
2328
if err != nil {
2429
return nil, fmt.Errorf("failed to resolve policy: %w", err)
@@ -33,7 +38,12 @@ func Verify(ctx context.Context, src *oci.ImageSpec, opts *policy.PolicyOptions)
3338
if opts.ReferrersRepo != "" {
3439
pctx.Mapping.Attestations = &config.ReferrersConfig{
3540
Repo: opts.ReferrersRepo,
36-
Style: config.AttestationSourceReferrers,
41+
Style: config.AttestationStyleReferrers,
42+
}
43+
} else if opts.AttestationStyle == config.AttestationStyleAttached {
44+
pctx.Mapping.Attestations = &config.ReferrersConfig{
45+
Repo: opts.ReferrersRepo,
46+
Style: config.AttestationStyleAttached,
3747
}
3848
}
3949
// because we have a mapping now, we can select a resolver based on its contents (ie. referrers or attached)

pkg/attestation/referrers_test.go

Lines changed: 123 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/docker/attest/internal/test"
1111
"github.com/docker/attest/pkg/attest"
1212
"github.com/docker/attest/pkg/attestation"
13+
"github.com/docker/attest/pkg/config"
1314
"github.com/docker/attest/pkg/mirror"
1415
"github.com/docker/attest/pkg/oci"
1516
"github.com/docker/attest/pkg/policy"
@@ -21,21 +22,29 @@ import (
2122
)
2223

2324
var (
24-
UnsignedTestImage = filepath.Join("..", "..", "test", "testdata", "unsigned-test-image")
25-
NoProvenanceImage = filepath.Join("..", "..", "test", "testdata", "no-provenance-image")
26-
PassPolicyDir = filepath.Join("..", "..", "test", "testdata", "local-policy-pass")
27-
PassNoTLPolicyDir = filepath.Join("..", "..", "test", "testdata", "local-policy-no-tl")
28-
FailPolicyDir = filepath.Join("..", "..", "test", "testdata", "local-policy-fail")
29-
TestTempDir = "attest-sign-test"
25+
UnsignedTestImage = filepath.Join("..", "..", "test", "testdata", "unsigned-test-image")
26+
NoProvenanceImage = filepath.Join("..", "..", "test", "testdata", "no-provenance-image")
27+
PassPolicyDir = filepath.Join("..", "..", "test", "testdata", "local-policy-pass")
28+
LocalPolicy = filepath.Join("..", "..", "test", "testdata", "local-policy")
29+
LocalPolicyAttached = filepath.Join("..", "..", "test", "testdata", "local-policy-attached")
30+
PassNoTLPolicyDir = filepath.Join("..", "..", "test", "testdata", "local-policy-no-tl")
31+
FailPolicyDir = filepath.Join("..", "..", "test", "testdata", "local-policy-fail")
32+
TestTempDir = "attest-sign-test"
3033
)
3134

3235
func TestAttestationReferenceTypes(t *testing.T) {
3336
ctx, signer := test.Setup(t)
37+
ctx = policy.WithPolicyEvaluator(ctx, policy.NewRegoEvaluator(true))
3438
platforms := []string{"linux/amd64", "linux/arm64"}
3539
for _, tc := range []struct {
36-
server *httptest.Server
37-
skipSubject bool
38-
useDigest bool
40+
server *httptest.Server
41+
referrersServer *httptest.Server
42+
skipSubject bool
43+
useDigest bool
44+
referrersRepo string
45+
attestationSource config.AttestationStyle
46+
expectFailure bool
47+
policyDir string
3948
}{
4049
{
4150
server: httptest.NewServer(registry.New(registry.WithReferrersSupport(true))),
@@ -44,76 +53,135 @@ func TestAttestationReferenceTypes(t *testing.T) {
4453
server: httptest.NewServer(registry.New()),
4554
},
4655
{
47-
server: httptest.NewServer(registry.New(registry.WithReferrersSupport(true))),
48-
skipSubject: true,
56+
server: httptest.NewServer(registry.New(registry.WithReferrersSupport(true))),
57+
skipSubject: true,
58+
attestationSource: config.AttestationStyleAttached,
4959
},
5060
{
5161
server: httptest.NewServer(registry.New(registry.WithReferrersSupport(true))),
5262
useDigest: true,
5363
},
64+
{
65+
server: httptest.NewServer(registry.New(registry.WithReferrersSupport(true))),
66+
expectFailure: true, //mismatched args
67+
attestationSource: config.AttestationStyleAttached,
68+
referrersRepo: "referrers",
69+
},
70+
{
71+
server: httptest.NewServer(registry.New(registry.WithReferrersSupport(true))),
72+
expectFailure: true, // no policy
73+
attestationSource: config.AttestationStyleReferrers,
74+
referrersRepo: "referrers",
75+
},
76+
{
77+
server: httptest.NewServer(registry.New(registry.WithReferrersSupport(true))),
78+
attestationSource: config.AttestationStyleReferrers,
79+
},
80+
{
81+
server: httptest.NewServer(registry.New(registry.WithReferrersSupport(false))),
82+
attestationSource: config.AttestationStyleReferrers,
83+
referrersServer: httptest.NewServer(registry.New(registry.WithReferrersSupport(true))),
84+
},
5485
} {
55-
s := tc.server
56-
defer s.Close()
57-
u, err := url.Parse(s.URL)
58-
require.NoError(t, err)
59-
60-
opts := &attestation.SigningOptions{
61-
Replace: true,
62-
SkipSubject: tc.skipSubject,
63-
}
64-
attIdx, err := oci.SubjectIndexFromPath(UnsignedTestImage)
65-
require.NoError(t, err)
66-
signedIndex, err := attest.Sign(ctx, attIdx.Index, signer, opts)
67-
require.NoError(t, err)
86+
t.Run(fmt.Sprint(tc), func(t *testing.T) {
87+
s := tc.server
88+
defer s.Close()
6889

69-
indexName := fmt.Sprintf("%s/repo:root", u.Host)
70-
require.NoError(t, err)
71-
err = mirror.PushIndexToRegistry(signedIndex, indexName)
72-
73-
for _, platform := range platforms {
74-
// can eval policy in the normal way
75-
ref := indexName
76-
if tc.useDigest {
77-
options := oci.WithOptions(ctx, nil)
78-
subjectRef, err := name.ParseReference(indexName)
79-
require.NoError(t, err)
80-
desc, err := remote.Index(subjectRef, options...)
81-
require.NoError(t, err)
82-
idxDigest, err := desc.Digest()
83-
require.NoError(t, err)
84-
ref = fmt.Sprintf("%s/repo@%s", u.Host, idxDigest.String())
90+
if tc.referrersServer != nil {
91+
defer tc.referrersServer.Close()
8592
}
93+
u, err := url.Parse(s.URL)
94+
require.NoError(t, err)
8695

87-
policyOpts := &policy.PolicyOptions{
88-
LocalPolicyDir: PassPolicyDir,
96+
opts := &attestation.SigningOptions{
97+
Replace: true,
98+
SkipSubject: tc.skipSubject,
8999
}
90-
src, err := oci.ParseImageSpec(ref, oci.WithPlatform(platform))
100+
attIdx, err := oci.SubjectIndexFromPath(UnsignedTestImage)
91101
require.NoError(t, err)
92-
results, err := attest.Verify(ctx, src, policyOpts)
102+
103+
indexName := fmt.Sprintf("%s/repo:root", u.Host)
93104
require.NoError(t, err)
94-
assert.Equal(t, attest.OutcomeSuccess, results.Outcome)
95105

96-
if !tc.skipSubject {
97-
// can evaluate policy using referrers
98-
if tc.useDigest {
99-
p, err := oci.ParsePlatform(platform)
106+
if tc.referrersServer != nil {
107+
ru, err := url.Parse(s.URL)
108+
require.NoError(t, err)
109+
repo := fmt.Sprintf("%s/referrers", ru.Host)
110+
tc.referrersRepo = repo
111+
images, err := attest.SignedAttestationImages(ctx, attIdx.Index, signer, opts)
112+
require.NoError(t, err)
113+
err = mirror.PushIndexToRegistry(attIdx.Index, indexName)
114+
for _, img := range images {
115+
err = mirror.PushImageToRegistry(img.Image, fmt.Sprintf("%s:tag-does-not-matter", repo))
100116
require.NoError(t, err)
101-
options := oci.WithOptions(ctx, p)
117+
}
118+
} else {
119+
signedIndex, err := attest.Sign(ctx, attIdx.Index, signer, opts)
120+
require.NoError(t, err)
121+
err = mirror.PushIndexToRegistry(signedIndex, indexName)
122+
require.NoError(t, err)
123+
}
124+
125+
for _, platform := range platforms {
126+
// can eval policy in the normal way
127+
ref := indexName
128+
if tc.useDigest {
129+
options := oci.WithOptions(ctx, nil)
102130
subjectRef, err := name.ParseReference(indexName)
103131
require.NoError(t, err)
104-
desc, err := remote.Image(subjectRef, options...)
132+
desc, err := remote.Index(subjectRef, options...)
105133
require.NoError(t, err)
106-
subjectDigest, err := desc.Digest()
134+
idxDigest, err := desc.Digest()
107135
require.NoError(t, err)
108-
ref = fmt.Sprintf("%s/repo@%s", u.Host, subjectDigest.String())
136+
ref = fmt.Sprintf("%s/repo@%s", u.Host, idxDigest.String())
137+
}
138+
139+
policyOpts := &policy.PolicyOptions{
140+
LocalPolicyDir: LocalPolicy,
141+
}
142+
if tc.policyDir != "" {
143+
policyOpts.LocalPolicyDir = tc.policyDir
144+
}
145+
146+
if tc.referrersRepo != "" {
147+
policyOpts.ReferrersRepo = tc.referrersRepo
148+
}
149+
150+
if tc.attestationSource != "" {
151+
policyOpts.AttestationStyle = tc.attestationSource
109152
}
110153
src, err := oci.ParseImageSpec(ref, oci.WithPlatform(platform))
111154
require.NoError(t, err)
112-
results, err = attest.Verify(ctx, src, policyOpts)
155+
results, err := attest.Verify(ctx, src, policyOpts)
156+
if tc.expectFailure {
157+
require.Error(t, err)
158+
continue
159+
}
113160
require.NoError(t, err)
114161
assert.Equal(t, attest.OutcomeSuccess, results.Outcome)
162+
163+
if !tc.skipSubject {
164+
// can evaluate policy using referrers
165+
if tc.useDigest {
166+
p, err := oci.ParsePlatform(platform)
167+
require.NoError(t, err)
168+
options := oci.WithOptions(ctx, p)
169+
subjectRef, err := name.ParseReference(indexName)
170+
require.NoError(t, err)
171+
desc, err := remote.Image(subjectRef, options...)
172+
require.NoError(t, err)
173+
subjectDigest, err := desc.Digest()
174+
require.NoError(t, err)
175+
ref = fmt.Sprintf("%s/repo@%s", u.Host, subjectDigest.String())
176+
}
177+
src, err := oci.ParseImageSpec(ref, oci.WithPlatform(platform))
178+
require.NoError(t, err)
179+
results, err = attest.Verify(ctx, src, policyOpts)
180+
require.NoError(t, err)
181+
assert.Equal(t, attest.OutcomeSuccess, results.Outcome)
182+
}
115183
}
116-
}
184+
})
117185
}
118186
}
119187

pkg/config/types.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
package config
22

33
type PolicyMappings struct {
4-
Version string `json:"version"`
5-
Kind string `json:"kind"`
6-
Policies []PolicyMapping `json:"policies"`
7-
Mirrors []PolicyMirror `json:"mirrors"`
4+
Version string `json:"version"`
5+
Kind string `json:"kind"`
6+
Policies []*PolicyMapping `json:"policies"`
7+
Mirrors []*PolicyMirror `json:"mirrors"`
88
}
99

10-
type AttestationSource string
10+
type AttestationStyle string
1111

1212
const (
13-
AttestationSourceAttached AttestationSource = "attached"
14-
AttestationSourceReferrers AttestationSource = "referrers"
13+
AttestationStyleAttached AttestationStyle = "attached"
14+
AttestationStyleReferrers AttestationStyle = "referrers"
1515
)
1616

1717
type PolicyMapping struct {
@@ -23,8 +23,8 @@ type PolicyMapping struct {
2323
}
2424

2525
type ReferrersConfig struct {
26-
Style AttestationSource `json:"style"`
27-
Repo string `json:"repo"`
26+
Style AttestationStyle `json:"style"`
27+
Repo string `json:"repo"`
2828
}
2929

3030
type PolicyMappingFile struct {

pkg/oci/registry.go

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -47,24 +47,16 @@ func (r *RegistryImageDetailsResolver) ImageDigest(ctx context.Context) (string,
4747
if err != nil {
4848
return "", fmt.Errorf("failed to parse reference: %w", err)
4949
}
50-
switch t := subjectRef.(type) {
51-
case name.Digest:
52-
// TODO should check if this is an index or an image
53-
r.digest = t.DigestStr()
54-
case name.Tag:
55-
options := WithOptions(ctx, r.Platform)
56-
desc, err := remote.Image(t, options...)
57-
if err != nil {
58-
return "", fmt.Errorf("failed to get image manifest: %w", err)
59-
}
60-
subjectDigest, err := desc.Digest()
61-
if err != nil {
62-
return "", fmt.Errorf("failed to get image digest: %w", err)
63-
}
64-
r.digest = subjectDigest.String()
65-
default:
66-
return "", fmt.Errorf("unsupported reference type: %T", t)
50+
options := WithOptions(ctx, r.Platform)
51+
desc, err := remote.Image(subjectRef, options...)
52+
if err != nil {
53+
return "", fmt.Errorf("failed to get image manifest: %w", err)
54+
}
55+
subjectDigest, err := desc.Digest()
56+
if err != nil {
57+
return "", fmt.Errorf("failed to get image digest: %w", err)
6758
}
59+
r.digest = subjectDigest.String()
6860
}
6961
return r.digest, nil
7062
}

pkg/policy/policy.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ func findPolicyMatch(named reference.Named, mappings *config.PolicyMappings) (*c
6363
for _, mapping := range mappings.Policies {
6464
if mapping.Origin.Domain == reference.Domain(named) &&
6565
strings.HasPrefix(reference.Path(named), mapping.Origin.Prefix) {
66-
return &mapping, nil
66+
return mapping, nil
6767
}
6868
}
6969
// now search mirrors
@@ -73,10 +73,10 @@ func findPolicyMatch(named reference.Named, mappings *config.PolicyMappings) (*c
7373
strings.HasPrefix(reference.Path(named), mirror.Mirror.Prefix) {
7474
for _, mapping := range mappings.Policies {
7575
if mapping.Id == mirror.PolicyId {
76-
return &mapping, nil
76+
return mapping, nil
7777
}
7878
}
79-
return nil, &mirror
79+
return nil, mirror
8080
}
8181
}
8282
}
@@ -92,7 +92,7 @@ func resolvePolicyById(opts *PolicyOptions) (*Policy, error) {
9292
if localMappings != nil {
9393
for _, mapping := range localMappings.Policies {
9494
if mapping.Id == opts.PolicyId {
95-
return resolveLocalPolicy(opts, &mapping)
95+
return resolveLocalPolicy(opts, mapping)
9696
}
9797
}
9898
}
@@ -104,7 +104,7 @@ func resolvePolicyById(opts *PolicyOptions) (*Policy, error) {
104104
}
105105
for _, mapping := range tufMappings.Policies {
106106
if mapping.Id == opts.PolicyId {
107-
return resolveTufPolicy(opts, &mapping)
107+
return resolveTufPolicy(opts, mapping)
108108
}
109109
}
110110
return nil, fmt.Errorf("policy with id %s not found", opts.PolicyId)
@@ -146,7 +146,7 @@ func ResolvePolicy(ctx context.Context, detailsResolver oci.ImageDetailsResolver
146146
if mirror != nil {
147147
for _, mapping := range tufMappings.Policies {
148148
if mapping.Id == mirror.PolicyId {
149-
return resolveTufPolicy(opts, &mapping)
149+
return resolveTufPolicy(opts, mapping)
150150
}
151151
}
152152
}
@@ -172,7 +172,7 @@ func CreateImageDetailsResolver(imageSource *oci.ImageSpec) (oci.ImageDetailsRes
172172
func CreateAttestationResolver(resolver oci.ImageDetailsResolver, mapping *config.PolicyMapping) (oci.AttestationResolver, error) {
173173
switch resolver := resolver.(type) {
174174
case *oci.RegistryImageDetailsResolver:
175-
if mapping.Attestations != nil && mapping.Attestations.Style == config.AttestationSourceAttached {
175+
if mapping.Attestations != nil && mapping.Attestations.Style == config.AttestationStyleAttached {
176176
return oci.NewRegistryAttestationResolver(resolver)
177177
} else {
178178
if mapping.Attestations != nil && mapping.Attestations.Repo != "" {

0 commit comments

Comments
 (0)