Merge pull request #201 from docker/feat--add-verifier-version-to-vsa
feat: add verifier version to vsa
This commit is contained in:
@@ -3,6 +3,7 @@ package attestation
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/attest/version"
|
||||
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
||||
"github.com/package-url/packageurl-go"
|
||||
)
|
||||
@@ -22,9 +23,12 @@ type VSAPredicate struct {
|
||||
}
|
||||
|
||||
type VSAVerifier struct {
|
||||
ID string `json:"id"`
|
||||
ID string `json:"id"`
|
||||
Version VerifierVersion `json:"version"`
|
||||
}
|
||||
|
||||
type VerifierVersion map[string]string
|
||||
|
||||
type VSAPolicy struct {
|
||||
URI string `json:"uri,omitempty"`
|
||||
Digest map[string]string `json:"digest"`
|
||||
@@ -44,3 +48,16 @@ func ToVSAResourceURI(sub intoto.Subject) (string, error) {
|
||||
purl.Qualifiers = packageurl.QualifiersFromMap(quals)
|
||||
return purl.String(), nil
|
||||
}
|
||||
|
||||
func GetVerifierVersion(fetcher version.Fetcher) (VerifierVersion, error) {
|
||||
attestVersion, err := fetcher.Get()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get attest version: %w", err)
|
||||
}
|
||||
if attestVersion == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return VerifierVersion{
|
||||
version.ThisModulePath: attestVersion.String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -33,13 +33,17 @@ func (e *InvalidVersionError) Error() string {
|
||||
}
|
||||
|
||||
func NewDefaultVersionChecker() *DefaultVersionChecker {
|
||||
return &DefaultVersionChecker{}
|
||||
return &DefaultVersionChecker{
|
||||
VersionFetcher: version.NewGoVersionFetcher(),
|
||||
}
|
||||
}
|
||||
|
||||
type DefaultVersionChecker struct{}
|
||||
type DefaultVersionChecker struct {
|
||||
VersionFetcher version.Fetcher
|
||||
}
|
||||
|
||||
func (vc *DefaultVersionChecker) CheckVersion(client Downloader) error {
|
||||
attestVersion, err := version.Get()
|
||||
attestVersion, err := vc.VersionFetcher.Get()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get version: %w", err)
|
||||
}
|
||||
|
||||
61
tuf/version_test.go
Normal file
61
tuf/version_test.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package tuf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/docker/attest/internal/test"
|
||||
"github.com/docker/attest/version"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const (
|
||||
invalidVersion = "0.0.1"
|
||||
validVersion = "v1.0.0-0"
|
||||
versionConstraint = ">=v1.0.0-0"
|
||||
)
|
||||
|
||||
func TestDefaultVersionChecker(t *testing.T) {
|
||||
testDir := test.CreateTempDir(t, "", "tuf_temp")
|
||||
versionConstraintsPath := filepath.Join(testDir, "version-constraints")
|
||||
err := os.WriteFile(versionConstraintsPath, []byte(versionConstraint), 0o600)
|
||||
assert.NoError(t, err)
|
||||
tufClient := NewMockTufClient(testDir)
|
||||
|
||||
expectedError := fmt.Sprintf("%s version %s does not satisfy constraints %s: %s is less than %s", version.ThisModulePath, invalidVersion, versionConstraint, invalidVersion, validVersion)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
expectedError string
|
||||
version string
|
||||
}{
|
||||
{name: "version is less than the minimum", expectedError: expectedError, version: "0.0.1"},
|
||||
{name: "version is equal to the minimum", version: "1.0.0"},
|
||||
{name: "version is greater than the minimum", version: "1.0.1"},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
checker := NewDefaultVersionChecker()
|
||||
checker.VersionFetcher = &MockVersionFetcher{version: tc.version}
|
||||
err := checker.CheckVersion(tufClient)
|
||||
if tc.expectedError != "" {
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, tc.expectedError, err.Error())
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type MockVersionFetcher struct {
|
||||
version string
|
||||
}
|
||||
|
||||
func (m *MockVersionFetcher) Get() (*semver.Version, error) {
|
||||
return semver.NewVersion(m.version)
|
||||
}
|
||||
@@ -19,10 +19,11 @@ func Set(ctx context.Context, userAgent string) context.Context {
|
||||
|
||||
// Get retrieves the HTTP user agent from the context.
|
||||
func Get(ctx context.Context) string {
|
||||
fetcher := version.NewGoVersionFetcher()
|
||||
if ua, ok := ctx.Value(userAgentKey).(string); ok {
|
||||
return ua
|
||||
}
|
||||
version, err := version.Get()
|
||||
version, err := fetcher.Get()
|
||||
if err != nil || version == nil {
|
||||
return defaultUserAgent
|
||||
}
|
||||
|
||||
20
verify.go
20
verify.go
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/docker/attest/oci"
|
||||
"github.com/docker/attest/policy"
|
||||
"github.com/docker/attest/tuf"
|
||||
"github.com/docker/attest/version"
|
||||
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
||||
)
|
||||
|
||||
@@ -21,6 +22,7 @@ type ImageVerifier struct {
|
||||
opts *policy.Options
|
||||
tufClient tuf.Downloader
|
||||
attestationVerifier attestation.Verifier
|
||||
versionFetcher version.Fetcher
|
||||
}
|
||||
|
||||
func NewImageVerifier(ctx context.Context, opts *policy.Options) (*ImageVerifier, error) {
|
||||
@@ -46,6 +48,7 @@ func NewImageVerifier(ctx context.Context, opts *policy.Options) (*ImageVerifier
|
||||
opts: opts,
|
||||
tufClient: tufClient,
|
||||
attestationVerifier: attestationVerifier,
|
||||
versionFetcher: version.NewGoVersionFetcher(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -93,7 +96,7 @@ func (verifier *ImageVerifier) Verify(ctx context.Context, src *oci.ImageSpec) (
|
||||
return nil, fmt.Errorf("failed to create attestation resolver: %w", err)
|
||||
}
|
||||
evaluator := policy.NewRegoEvaluator(verifier.opts.Debug, verifier.attestationVerifier)
|
||||
result, err = verifyAttestations(ctx, resolver, evaluator, resolvedPolicy, verifier.opts)
|
||||
result, err = verifier.verifyAttestations(ctx, resolver, evaluator, resolvedPolicy)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to evaluate policy: %w", err)
|
||||
}
|
||||
@@ -141,7 +144,7 @@ func defaultLocalTargetsDir() (string, error) {
|
||||
return filepath.Join(homeDir, ".docker", "tuf"), nil
|
||||
}
|
||||
|
||||
func toVerificationResult(p *policy.Policy, input *policy.Input, result *policy.Result) (*VerificationResult, error) {
|
||||
func toVerificationResult(p *policy.Policy, input *policy.Input, result *policy.Result, versionFetcher version.Fetcher) (*VerificationResult, error) {
|
||||
dgst, err := oci.SplitDigest(input.Digest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to split digest: %w", err)
|
||||
@@ -168,6 +171,10 @@ func toVerificationResult(p *policy.Policy, input *policy.Input, result *policy.
|
||||
}
|
||||
|
||||
vsaPolicy := attestation.VSAPolicy{URI: result.Summary.PolicyURI, DownloadLocation: p.URI, Digest: p.Digest}
|
||||
attestVersion, err := attestation.GetVerifierVersion(versionFetcher)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get verifier version: %w", err)
|
||||
}
|
||||
|
||||
return &VerificationResult{
|
||||
Policy: p,
|
||||
@@ -182,7 +189,8 @@ func toVerificationResult(p *policy.Policy, input *policy.Input, result *policy.
|
||||
},
|
||||
Predicate: attestation.VSAPredicate{
|
||||
Verifier: attestation.VSAVerifier{
|
||||
ID: result.Summary.Verifier,
|
||||
ID: result.Summary.Verifier,
|
||||
Version: attestVersion,
|
||||
},
|
||||
TimeVerified: time.Now().UTC().Format(time.RFC3339),
|
||||
ResourceURI: resourceURI,
|
||||
@@ -195,7 +203,7 @@ func toVerificationResult(p *policy.Policy, input *policy.Input, result *policy.
|
||||
}, nil
|
||||
}
|
||||
|
||||
func verifyAttestations(ctx context.Context, resolver attestation.Resolver, evaluator policy.Evaluator, resolvedPolicy *policy.Policy, opts *policy.Options) (*VerificationResult, error) {
|
||||
func (verifier *ImageVerifier) verifyAttestations(ctx context.Context, resolver attestation.Resolver, evaluator policy.Evaluator, resolvedPolicy *policy.Policy) (*VerificationResult, error) {
|
||||
desc, err := resolver.ImageDescriptor(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get image descriptor: %w", err)
|
||||
@@ -247,7 +255,7 @@ func verifyAttestations(ctx context.Context, resolver attestation.Resolver, eval
|
||||
Domain: reference.Domain(ref),
|
||||
NormalizedName: reference.Path(ref),
|
||||
FamiliarName: reference.FamiliarName(ref),
|
||||
Parameters: opts.Parameters,
|
||||
Parameters: verifier.opts.Parameters,
|
||||
}
|
||||
// rego has null strings
|
||||
if tag != "" {
|
||||
@@ -257,7 +265,7 @@ func verifyAttestations(ctx context.Context, resolver attestation.Resolver, eval
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("policy evaluation failed: %w", err)
|
||||
}
|
||||
verificationResult, err := toVerificationResult(resolvedPolicy, input, result)
|
||||
verificationResult, err := toVerificationResult(resolvedPolicy, input, result, verifier.versionFetcher)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert to policy result: %w", err)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/attest/attestation"
|
||||
"github.com/docker/attest/internal/test"
|
||||
@@ -17,6 +18,7 @@ import (
|
||||
"github.com/docker/attest/policy"
|
||||
"github.com/docker/attest/tlog"
|
||||
"github.com/docker/attest/tuf"
|
||||
"github.com/docker/attest/version"
|
||||
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
||||
"github.com/secure-systems-lab/go-securesystemslib/dsse"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -32,9 +34,16 @@ var (
|
||||
)
|
||||
|
||||
const (
|
||||
LinuxAMD64 = "linux/amd64"
|
||||
LinuxAMD64 = "linux/amd64"
|
||||
TestVerifierVersion = "9.9.9"
|
||||
)
|
||||
|
||||
type MockVersionFetcher struct{}
|
||||
|
||||
func (m *MockVersionFetcher) Get() (*semver.Version, error) {
|
||||
return semver.NewVersion(TestVerifierVersion)
|
||||
}
|
||||
|
||||
func TestVerifyAttestations(t *testing.T) {
|
||||
ex, err := os.ReadFile(ExampleAttestation)
|
||||
assert.NoError(t, err)
|
||||
@@ -62,7 +71,9 @@ func TestVerifyAttestations(t *testing.T) {
|
||||
return policy.AllowedResult(), tc.policyEvaluationError
|
||||
},
|
||||
}
|
||||
_, err := verifyAttestations(ctx, resolver, &mockPE, &policy.Policy{ResolvedName: ""}, &policy.Options{})
|
||||
verifier, err := NewImageVerifier(ctx, &policy.Options{})
|
||||
require.NoError(t, err)
|
||||
_, err = verifier.verifyAttestations(ctx, resolver, &mockPE, &policy.Policy{ResolvedName: ""})
|
||||
if tc.expectedError != nil {
|
||||
if assert.Error(t, err) {
|
||||
assert.Equal(t, tc.expectedError.Error(), err.Error())
|
||||
@@ -102,7 +113,10 @@ func TestVSA(t *testing.T) {
|
||||
AttestationStyle: mapping.AttestationStyleAttached,
|
||||
DisableTUF: true,
|
||||
}
|
||||
results, err := Verify(ctx, spec, policyOpts)
|
||||
verifier, err := NewImageVerifier(ctx, policyOpts)
|
||||
require.NoError(t, err)
|
||||
verifier.versionFetcher = &MockVersionFetcher{}
|
||||
results, err := verifier.Verify(ctx, spec)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, OutcomeSuccess, results.Outcome)
|
||||
assert.Empty(t, results.Violations)
|
||||
@@ -135,6 +149,7 @@ func TestVSA(t *testing.T) {
|
||||
assert.NotEmpty(t, digest)
|
||||
assert.Contains(t, []string{"application/vnd.in-toto.provenance+dsse", "application/vnd.in-toto.spdx+dsse"}, input.MediaType)
|
||||
}
|
||||
assert.Equal(t, TestVerifierVersion, attestationPredicate.Verifier.Version[version.ThisModulePath])
|
||||
}
|
||||
|
||||
func TestVerificationFailure(t *testing.T) {
|
||||
|
||||
@@ -9,9 +9,19 @@ import (
|
||||
|
||||
const ThisModulePath = "github.com/docker/attest"
|
||||
|
||||
type Fetcher interface {
|
||||
Get() (*semver.Version, error)
|
||||
}
|
||||
|
||||
type GoModVersionFetcher struct{}
|
||||
|
||||
func NewGoVersionFetcher() *GoModVersionFetcher {
|
||||
return &GoModVersionFetcher{}
|
||||
}
|
||||
|
||||
// Get returns the version of the attest module.
|
||||
// this can return nil if the version can't be determined (without an error).
|
||||
func Get() (*semver.Version, error) {
|
||||
func (*GoModVersionFetcher) Get() (*semver.Version, error) {
|
||||
var attestMod *debug.Module
|
||||
bi, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
|
||||
Reference in New Issue
Block a user