feat: add verifier version to vsa
This commit is contained in:
@@ -3,6 +3,7 @@ package attestation
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/docker/attest/version"
|
||||||
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
||||||
"github.com/package-url/packageurl-go"
|
"github.com/package-url/packageurl-go"
|
||||||
)
|
)
|
||||||
@@ -22,9 +23,12 @@ type VSAPredicate struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type VSAVerifier struct {
|
type VSAVerifier struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
|
Version VerifierVersion `json:"version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type VerifierVersion map[string]string
|
||||||
|
|
||||||
type VSAPolicy struct {
|
type VSAPolicy struct {
|
||||||
URI string `json:"uri,omitempty"`
|
URI string `json:"uri,omitempty"`
|
||||||
Digest map[string]string `json:"digest"`
|
Digest map[string]string `json:"digest"`
|
||||||
@@ -44,3 +48,16 @@ func ToVSAResourceURI(sub intoto.Subject) (string, error) {
|
|||||||
purl.Qualifiers = packageurl.QualifiersFromMap(quals)
|
purl.Qualifiers = packageurl.QualifiersFromMap(quals)
|
||||||
return purl.String(), nil
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -39,7 +39,8 @@ func NewDefaultVersionChecker() *DefaultVersionChecker {
|
|||||||
type DefaultVersionChecker struct{}
|
type DefaultVersionChecker struct{}
|
||||||
|
|
||||||
func (vc *DefaultVersionChecker) CheckVersion(client Downloader) error {
|
func (vc *DefaultVersionChecker) CheckVersion(client Downloader) error {
|
||||||
attestVersion, err := version.Get()
|
fetcher := version.NewGoVersionFetcher()
|
||||||
|
attestVersion, err := fetcher.Get()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get version: %w", err)
|
return fmt.Errorf("failed to get version: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,10 +19,11 @@ func Set(ctx context.Context, userAgent string) context.Context {
|
|||||||
|
|
||||||
// Get retrieves the HTTP user agent from the context.
|
// Get retrieves the HTTP user agent from the context.
|
||||||
func Get(ctx context.Context) string {
|
func Get(ctx context.Context) string {
|
||||||
|
fetcher := version.NewGoVersionFetcher()
|
||||||
if ua, ok := ctx.Value(userAgentKey).(string); ok {
|
if ua, ok := ctx.Value(userAgentKey).(string); ok {
|
||||||
return ua
|
return ua
|
||||||
}
|
}
|
||||||
version, err := version.Get()
|
version, err := fetcher.Get()
|
||||||
if err != nil || version == nil {
|
if err != nil || version == nil {
|
||||||
return defaultUserAgent
|
return defaultUserAgent
|
||||||
}
|
}
|
||||||
|
|||||||
20
verify.go
20
verify.go
@@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/docker/attest/oci"
|
"github.com/docker/attest/oci"
|
||||||
"github.com/docker/attest/policy"
|
"github.com/docker/attest/policy"
|
||||||
"github.com/docker/attest/tuf"
|
"github.com/docker/attest/tuf"
|
||||||
|
"github.com/docker/attest/version"
|
||||||
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -21,6 +22,7 @@ type ImageVerifier struct {
|
|||||||
opts *policy.Options
|
opts *policy.Options
|
||||||
tufClient tuf.Downloader
|
tufClient tuf.Downloader
|
||||||
attestationVerifier attestation.Verifier
|
attestationVerifier attestation.Verifier
|
||||||
|
versionFetcher version.Fetcher
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewImageVerifier(ctx context.Context, opts *policy.Options) (*ImageVerifier, error) {
|
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,
|
opts: opts,
|
||||||
tufClient: tufClient,
|
tufClient: tufClient,
|
||||||
attestationVerifier: attestationVerifier,
|
attestationVerifier: attestationVerifier,
|
||||||
|
versionFetcher: version.NewGoVersionFetcher(),
|
||||||
}, nil
|
}, 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)
|
return nil, fmt.Errorf("failed to create attestation resolver: %w", err)
|
||||||
}
|
}
|
||||||
evaluator := policy.NewRegoEvaluator(verifier.opts.Debug, verifier.attestationVerifier)
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to evaluate policy: %w", err)
|
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
|
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)
|
dgst, err := oci.SplitDigest(input.Digest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to split digest: %w", err)
|
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}
|
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{
|
return &VerificationResult{
|
||||||
Policy: p,
|
Policy: p,
|
||||||
@@ -182,7 +189,8 @@ func toVerificationResult(p *policy.Policy, input *policy.Input, result *policy.
|
|||||||
},
|
},
|
||||||
Predicate: attestation.VSAPredicate{
|
Predicate: attestation.VSAPredicate{
|
||||||
Verifier: attestation.VSAVerifier{
|
Verifier: attestation.VSAVerifier{
|
||||||
ID: result.Summary.Verifier,
|
ID: result.Summary.Verifier,
|
||||||
|
Version: attestVersion,
|
||||||
},
|
},
|
||||||
TimeVerified: time.Now().UTC().Format(time.RFC3339),
|
TimeVerified: time.Now().UTC().Format(time.RFC3339),
|
||||||
ResourceURI: resourceURI,
|
ResourceURI: resourceURI,
|
||||||
@@ -195,7 +203,7 @@ func toVerificationResult(p *policy.Policy, input *policy.Input, result *policy.
|
|||||||
}, nil
|
}, 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)
|
desc, err := resolver.ImageDescriptor(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get image descriptor: %w", err)
|
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),
|
Domain: reference.Domain(ref),
|
||||||
NormalizedName: reference.Path(ref),
|
NormalizedName: reference.Path(ref),
|
||||||
FamiliarName: reference.FamiliarName(ref),
|
FamiliarName: reference.FamiliarName(ref),
|
||||||
Parameters: opts.Parameters,
|
Parameters: verifier.opts.Parameters,
|
||||||
}
|
}
|
||||||
// rego has null strings
|
// rego has null strings
|
||||||
if tag != "" {
|
if tag != "" {
|
||||||
@@ -257,7 +265,7 @@ func verifyAttestations(ctx context.Context, resolver attestation.Resolver, eval
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("policy evaluation failed: %w", err)
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to convert to policy result: %w", err)
|
return nil, fmt.Errorf("failed to convert to policy result: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/Masterminds/semver/v3"
|
||||||
"github.com/distribution/reference"
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/attest/attestation"
|
"github.com/docker/attest/attestation"
|
||||||
"github.com/docker/attest/internal/test"
|
"github.com/docker/attest/internal/test"
|
||||||
@@ -17,6 +18,7 @@ import (
|
|||||||
"github.com/docker/attest/policy"
|
"github.com/docker/attest/policy"
|
||||||
"github.com/docker/attest/tlog"
|
"github.com/docker/attest/tlog"
|
||||||
"github.com/docker/attest/tuf"
|
"github.com/docker/attest/tuf"
|
||||||
|
"github.com/docker/attest/version"
|
||||||
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
||||||
"github.com/secure-systems-lab/go-securesystemslib/dsse"
|
"github.com/secure-systems-lab/go-securesystemslib/dsse"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -32,9 +34,16 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
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) {
|
func TestVerifyAttestations(t *testing.T) {
|
||||||
ex, err := os.ReadFile(ExampleAttestation)
|
ex, err := os.ReadFile(ExampleAttestation)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@@ -62,7 +71,9 @@ func TestVerifyAttestations(t *testing.T) {
|
|||||||
return policy.AllowedResult(), tc.policyEvaluationError
|
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 tc.expectedError != nil {
|
||||||
if assert.Error(t, err) {
|
if assert.Error(t, err) {
|
||||||
assert.Equal(t, tc.expectedError.Error(), err.Error())
|
assert.Equal(t, tc.expectedError.Error(), err.Error())
|
||||||
@@ -102,7 +113,10 @@ func TestVSA(t *testing.T) {
|
|||||||
AttestationStyle: mapping.AttestationStyleAttached,
|
AttestationStyle: mapping.AttestationStyleAttached,
|
||||||
DisableTUF: true,
|
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)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, OutcomeSuccess, results.Outcome)
|
assert.Equal(t, OutcomeSuccess, results.Outcome)
|
||||||
assert.Empty(t, results.Violations)
|
assert.Empty(t, results.Violations)
|
||||||
@@ -135,6 +149,7 @@ func TestVSA(t *testing.T) {
|
|||||||
assert.NotEmpty(t, digest)
|
assert.NotEmpty(t, digest)
|
||||||
assert.Contains(t, []string{"application/vnd.in-toto.provenance+dsse", "application/vnd.in-toto.spdx+dsse"}, input.MediaType)
|
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) {
|
func TestVerificationFailure(t *testing.T) {
|
||||||
|
|||||||
@@ -9,9 +9,19 @@ import (
|
|||||||
|
|
||||||
const ThisModulePath = "github.com/docker/attest"
|
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.
|
// Get returns the version of the attest module.
|
||||||
// this can return nil if the version can't be determined (without an error).
|
// 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
|
var attestMod *debug.Module
|
||||||
bi, ok := debug.ReadBuildInfo()
|
bi, ok := debug.ReadBuildInfo()
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|||||||
Reference in New Issue
Block a user