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

Commit 17b0978

Browse files
authored
Merge pull request #201 from docker/feat--add-verifier-version-to-vsa
feat: add verifier version to vsa
2 parents eda0b23 + 7ff20a9 commit 17b0978

File tree

7 files changed

+131
-15
lines changed

7 files changed

+131
-15
lines changed

attestation/vsa.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package attestation
33
import (
44
"fmt"
55

6+
"github.com/docker/attest/version"
67
intoto "github.com/in-toto/in-toto-golang/in_toto"
78
"github.com/package-url/packageurl-go"
89
)
@@ -22,9 +23,12 @@ type VSAPredicate struct {
2223
}
2324

2425
type VSAVerifier struct {
25-
ID string `json:"id"`
26+
ID string `json:"id"`
27+
Version VerifierVersion `json:"version"`
2628
}
2729

30+
type VerifierVersion map[string]string
31+
2832
type VSAPolicy struct {
2933
URI string `json:"uri,omitempty"`
3034
Digest map[string]string `json:"digest"`
@@ -44,3 +48,16 @@ func ToVSAResourceURI(sub intoto.Subject) (string, error) {
4448
purl.Qualifiers = packageurl.QualifiersFromMap(quals)
4549
return purl.String(), nil
4650
}
51+
52+
func GetVerifierVersion(fetcher version.Fetcher) (VerifierVersion, error) {
53+
attestVersion, err := fetcher.Get()
54+
if err != nil {
55+
return nil, fmt.Errorf("failed to get attest version: %w", err)
56+
}
57+
if attestVersion == nil {
58+
return nil, nil
59+
}
60+
return VerifierVersion{
61+
version.ThisModulePath: attestVersion.String(),
62+
}, nil
63+
}

tuf/version.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,17 @@ func (e *InvalidVersionError) Error() string {
3333
}
3434

3535
func NewDefaultVersionChecker() *DefaultVersionChecker {
36-
return &DefaultVersionChecker{}
36+
return &DefaultVersionChecker{
37+
VersionFetcher: version.NewGoVersionFetcher(),
38+
}
3739
}
3840

39-
type DefaultVersionChecker struct{}
41+
type DefaultVersionChecker struct {
42+
VersionFetcher version.Fetcher
43+
}
4044

4145
func (vc *DefaultVersionChecker) CheckVersion(client Downloader) error {
42-
attestVersion, err := version.Get()
46+
attestVersion, err := vc.VersionFetcher.Get()
4347
if err != nil {
4448
return fmt.Errorf("failed to get version: %w", err)
4549
}

tuf/version_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package tuf
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
"testing"
8+
9+
"github.com/Masterminds/semver/v3"
10+
"github.com/docker/attest/internal/test"
11+
"github.com/docker/attest/version"
12+
"github.com/stretchr/testify/assert"
13+
)
14+
15+
const (
16+
invalidVersion = "0.0.1"
17+
validVersion = "v1.0.0-0"
18+
versionConstraint = ">=v1.0.0-0"
19+
)
20+
21+
func TestDefaultVersionChecker(t *testing.T) {
22+
testDir := test.CreateTempDir(t, "", "tuf_temp")
23+
versionConstraintsPath := filepath.Join(testDir, "version-constraints")
24+
err := os.WriteFile(versionConstraintsPath, []byte(versionConstraint), 0o600)
25+
assert.NoError(t, err)
26+
tufClient := NewMockTufClient(testDir)
27+
28+
expectedError := fmt.Sprintf("%s version %s does not satisfy constraints %s: %s is less than %s", version.ThisModulePath, invalidVersion, versionConstraint, invalidVersion, validVersion)
29+
30+
testCases := []struct {
31+
name string
32+
expectedError string
33+
version string
34+
}{
35+
{name: "version is less than the minimum", expectedError: expectedError, version: "0.0.1"},
36+
{name: "version is equal to the minimum", version: "1.0.0"},
37+
{name: "version is greater than the minimum", version: "1.0.1"},
38+
}
39+
40+
for _, tc := range testCases {
41+
t.Run(tc.name, func(t *testing.T) {
42+
checker := NewDefaultVersionChecker()
43+
checker.VersionFetcher = &MockVersionFetcher{version: tc.version}
44+
err := checker.CheckVersion(tufClient)
45+
if tc.expectedError != "" {
46+
assert.Error(t, err)
47+
assert.Equal(t, tc.expectedError, err.Error())
48+
return
49+
}
50+
assert.NoError(t, err)
51+
})
52+
}
53+
}
54+
55+
type MockVersionFetcher struct {
56+
version string
57+
}
58+
59+
func (m *MockVersionFetcher) Get() (*semver.Version, error) {
60+
return semver.NewVersion(m.version)
61+
}

useragent/useragent.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@ func Set(ctx context.Context, userAgent string) context.Context {
1919

2020
// Get retrieves the HTTP user agent from the context.
2121
func Get(ctx context.Context) string {
22+
fetcher := version.NewGoVersionFetcher()
2223
if ua, ok := ctx.Value(userAgentKey).(string); ok {
2324
return ua
2425
}
25-
version, err := version.Get()
26+
version, err := fetcher.Get()
2627
if err != nil || version == nil {
2728
return defaultUserAgent
2829
}

verify.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@ import (
1414
"github.com/docker/attest/oci"
1515
"github.com/docker/attest/policy"
1616
"github.com/docker/attest/tuf"
17+
"github.com/docker/attest/version"
1718
intoto "github.com/in-toto/in-toto-golang/in_toto"
1819
)
1920

2021
type ImageVerifier struct {
2122
opts *policy.Options
2223
tufClient tuf.Downloader
2324
attestationVerifier attestation.Verifier
25+
versionFetcher version.Fetcher
2426
}
2527

2628
func NewImageVerifier(ctx context.Context, opts *policy.Options) (*ImageVerifier, error) {
@@ -46,6 +48,7 @@ func NewImageVerifier(ctx context.Context, opts *policy.Options) (*ImageVerifier
4648
opts: opts,
4749
tufClient: tufClient,
4850
attestationVerifier: attestationVerifier,
51+
versionFetcher: version.NewGoVersionFetcher(),
4952
}, nil
5053
}
5154

@@ -93,7 +96,7 @@ func (verifier *ImageVerifier) Verify(ctx context.Context, src *oci.ImageSpec) (
9396
return nil, fmt.Errorf("failed to create attestation resolver: %w", err)
9497
}
9598
evaluator := policy.NewRegoEvaluator(verifier.opts.Debug, verifier.attestationVerifier)
96-
result, err = verifyAttestations(ctx, resolver, evaluator, resolvedPolicy, verifier.opts)
99+
result, err = verifier.verifyAttestations(ctx, resolver, evaluator, resolvedPolicy)
97100
if err != nil {
98101
return nil, fmt.Errorf("failed to evaluate policy: %w", err)
99102
}
@@ -141,7 +144,7 @@ func defaultLocalTargetsDir() (string, error) {
141144
return filepath.Join(homeDir, ".docker", "tuf"), nil
142145
}
143146

144-
func toVerificationResult(p *policy.Policy, input *policy.Input, result *policy.Result) (*VerificationResult, error) {
147+
func toVerificationResult(p *policy.Policy, input *policy.Input, result *policy.Result, versionFetcher version.Fetcher) (*VerificationResult, error) {
145148
dgst, err := oci.SplitDigest(input.Digest)
146149
if err != nil {
147150
return nil, fmt.Errorf("failed to split digest: %w", err)
@@ -168,6 +171,10 @@ func toVerificationResult(p *policy.Policy, input *policy.Input, result *policy.
168171
}
169172

170173
vsaPolicy := attestation.VSAPolicy{URI: result.Summary.PolicyURI, DownloadLocation: p.URI, Digest: p.Digest}
174+
attestVersion, err := attestation.GetVerifierVersion(versionFetcher)
175+
if err != nil {
176+
return nil, fmt.Errorf("failed to get verifier version: %w", err)
177+
}
171178

172179
return &VerificationResult{
173180
Policy: p,
@@ -182,7 +189,8 @@ func toVerificationResult(p *policy.Policy, input *policy.Input, result *policy.
182189
},
183190
Predicate: attestation.VSAPredicate{
184191
Verifier: attestation.VSAVerifier{
185-
ID: result.Summary.Verifier,
192+
ID: result.Summary.Verifier,
193+
Version: attestVersion,
186194
},
187195
TimeVerified: time.Now().UTC().Format(time.RFC3339),
188196
ResourceURI: resourceURI,
@@ -195,7 +203,7 @@ func toVerificationResult(p *policy.Policy, input *policy.Input, result *policy.
195203
}, nil
196204
}
197205

198-
func verifyAttestations(ctx context.Context, resolver attestation.Resolver, evaluator policy.Evaluator, resolvedPolicy *policy.Policy, opts *policy.Options) (*VerificationResult, error) {
206+
func (verifier *ImageVerifier) verifyAttestations(ctx context.Context, resolver attestation.Resolver, evaluator policy.Evaluator, resolvedPolicy *policy.Policy) (*VerificationResult, error) {
199207
desc, err := resolver.ImageDescriptor(ctx)
200208
if err != nil {
201209
return nil, fmt.Errorf("failed to get image descriptor: %w", err)
@@ -247,7 +255,7 @@ func verifyAttestations(ctx context.Context, resolver attestation.Resolver, eval
247255
Domain: reference.Domain(ref),
248256
NormalizedName: reference.Path(ref),
249257
FamiliarName: reference.FamiliarName(ref),
250-
Parameters: opts.Parameters,
258+
Parameters: verifier.opts.Parameters,
251259
}
252260
// rego has null strings
253261
if tag != "" {
@@ -257,7 +265,7 @@ func verifyAttestations(ctx context.Context, resolver attestation.Resolver, eval
257265
if err != nil {
258266
return nil, fmt.Errorf("policy evaluation failed: %w", err)
259267
}
260-
verificationResult, err := toVerificationResult(resolvedPolicy, input, result)
268+
verificationResult, err := toVerificationResult(resolvedPolicy, input, result, verifier.versionFetcher)
261269
if err != nil {
262270
return nil, fmt.Errorf("failed to convert to policy result: %w", err)
263271
}

verify_test.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"testing"
1010
"time"
1111

12+
"github.com/Masterminds/semver/v3"
1213
"github.com/distribution/reference"
1314
"github.com/docker/attest/attestation"
1415
"github.com/docker/attest/internal/test"
@@ -17,6 +18,7 @@ import (
1718
"github.com/docker/attest/policy"
1819
"github.com/docker/attest/tlog"
1920
"github.com/docker/attest/tuf"
21+
"github.com/docker/attest/version"
2022
intoto "github.com/in-toto/in-toto-golang/in_toto"
2123
"github.com/secure-systems-lab/go-securesystemslib/dsse"
2224
"github.com/stretchr/testify/assert"
@@ -32,9 +34,16 @@ var (
3234
)
3335

3436
const (
35-
LinuxAMD64 = "linux/amd64"
37+
LinuxAMD64 = "linux/amd64"
38+
TestVerifierVersion = "9.9.9"
3639
)
3740

41+
type MockVersionFetcher struct{}
42+
43+
func (m *MockVersionFetcher) Get() (*semver.Version, error) {
44+
return semver.NewVersion(TestVerifierVersion)
45+
}
46+
3847
func TestVerifyAttestations(t *testing.T) {
3948
ex, err := os.ReadFile(ExampleAttestation)
4049
assert.NoError(t, err)
@@ -62,7 +71,9 @@ func TestVerifyAttestations(t *testing.T) {
6271
return policy.AllowedResult(), tc.policyEvaluationError
6372
},
6473
}
65-
_, err := verifyAttestations(ctx, resolver, &mockPE, &policy.Policy{ResolvedName: ""}, &policy.Options{})
74+
verifier, err := NewImageVerifier(ctx, &policy.Options{})
75+
require.NoError(t, err)
76+
_, err = verifier.verifyAttestations(ctx, resolver, &mockPE, &policy.Policy{ResolvedName: ""})
6677
if tc.expectedError != nil {
6778
if assert.Error(t, err) {
6879
assert.Equal(t, tc.expectedError.Error(), err.Error())
@@ -102,7 +113,10 @@ func TestVSA(t *testing.T) {
102113
AttestationStyle: mapping.AttestationStyleAttached,
103114
DisableTUF: true,
104115
}
105-
results, err := Verify(ctx, spec, policyOpts)
116+
verifier, err := NewImageVerifier(ctx, policyOpts)
117+
require.NoError(t, err)
118+
verifier.versionFetcher = &MockVersionFetcher{}
119+
results, err := verifier.Verify(ctx, spec)
106120
require.NoError(t, err)
107121
assert.Equal(t, OutcomeSuccess, results.Outcome)
108122
assert.Empty(t, results.Violations)
@@ -135,6 +149,7 @@ func TestVSA(t *testing.T) {
135149
assert.NotEmpty(t, digest)
136150
assert.Contains(t, []string{"application/vnd.in-toto.provenance+dsse", "application/vnd.in-toto.spdx+dsse"}, input.MediaType)
137151
}
152+
assert.Equal(t, TestVerifierVersion, attestationPredicate.Verifier.Version[version.ThisModulePath])
138153
}
139154

140155
func TestVerificationFailure(t *testing.T) {

version/version.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,19 @@ import (
99

1010
const ThisModulePath = "github.com/docker/attest"
1111

12+
type Fetcher interface {
13+
Get() (*semver.Version, error)
14+
}
15+
16+
type GoModVersionFetcher struct{}
17+
18+
func NewGoVersionFetcher() *GoModVersionFetcher {
19+
return &GoModVersionFetcher{}
20+
}
21+
1222
// Get returns the version of the attest module.
1323
// this can return nil if the version can't be determined (without an error).
14-
func Get() (*semver.Version, error) {
24+
func (*GoModVersionFetcher) Get() (*semver.Version, error) {
1525
var attestMod *debug.Module
1626
bi, ok := debug.ReadBuildInfo()
1727
if !ok {

0 commit comments

Comments
 (0)