Return results from rego evaluation (#14)
This commit is contained in:
@@ -4,8 +4,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/attest/pkg/attestation"
|
||||
"github.com/docker/attest/pkg/oci"
|
||||
@@ -142,9 +140,16 @@ func SignIndexAttestations(ctx context.Context, idx v1.ImageIndex, signer dsse.S
|
||||
}
|
||||
|
||||
if opts.VSAOptions != nil {
|
||||
newImg, err = addVSA(ctx, newImg, statements, manifest.MediaType, signer, opts)
|
||||
newLayer, err := generateVSA(ctx, newImg, statements, signer, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to add VSA: %w", err)
|
||||
return nil, fmt.Errorf("failed to generate VSA: %w", err)
|
||||
}
|
||||
vsaReplace := &SigningOptions{
|
||||
Replace: false,
|
||||
}
|
||||
newImg, err = addSignedLayers([]mutate.Addendum{*newLayer}, layers, manifest.MediaType, newImg, vsaReplace)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to add VSA layer: %w", err)
|
||||
}
|
||||
}
|
||||
newDesc, err := partial.Descriptor(newImg)
|
||||
@@ -171,93 +176,6 @@ func SignIndexAttestations(ctx context.Context, idx v1.ImageIndex, signer dsse.S
|
||||
return newIndex, nil
|
||||
}
|
||||
|
||||
func addVSA(ctx context.Context, image v1.Image, stmt []*intoto.Statement, outerMediaType types.MediaType, signer dsse.SignerVerifier, opts *SigningOptions) (v1.Image, error) {
|
||||
if len(stmt) == 0 {
|
||||
return nil, fmt.Errorf("no attestations found to generate VSA from")
|
||||
}
|
||||
sub := stmt[0].Subject[0]
|
||||
stype := stmt[0].Type
|
||||
|
||||
uri, err := attestation.ToVSAResourceURI(sub)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate VSA resource URI: %w", err)
|
||||
}
|
||||
|
||||
inputs := make([]attestation.VSAInputAttestation, 0, len(stmt))
|
||||
layers, err := image.Layers()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get layers: %w", err)
|
||||
}
|
||||
for _, layer := range layers {
|
||||
mt, err := layer.MediaType()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get layer media type: %w", err)
|
||||
}
|
||||
mediaType := string(mt)
|
||||
if !strings.HasPrefix(mediaType, "application/vnd.in-toto.") ||
|
||||
!strings.HasSuffix(mediaType, "+dsse") {
|
||||
continue
|
||||
}
|
||||
|
||||
dgst, err := layer.Digest()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get layer digest: %w", err)
|
||||
}
|
||||
inputs = append(inputs, attestation.VSAInputAttestation{
|
||||
Digest: map[string]string{"sha256": dgst.Hex},
|
||||
MediaType: string(mt),
|
||||
})
|
||||
}
|
||||
vsaStatement := &intoto.Statement{
|
||||
StatementHeader: intoto.StatementHeader{
|
||||
PredicateType: attestation.VSAPredicateType,
|
||||
Type: stype,
|
||||
Subject: stmt[0].Subject,
|
||||
},
|
||||
Predicate: attestation.VSAPredicate{
|
||||
Verifier: attestation.VSAVerifier{
|
||||
ID: opts.VSAOptions.VerifierID,
|
||||
},
|
||||
TimeVerified: time.Now().UTC().Format(time.RFC3339),
|
||||
ResourceUri: uri,
|
||||
Policy: attestation.VSAPolicy{URI: opts.VSAOptions.PolicyURI},
|
||||
VerificationResult: "PASSED",
|
||||
VerifiedLevels: []string{opts.VSAOptions.BuildLevel},
|
||||
InputAttestations: inputs,
|
||||
},
|
||||
}
|
||||
payload, err := json.Marshal(vsaStatement)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal statement: %w", err)
|
||||
}
|
||||
env, err := attestation.SignDSSE(ctx, payload, intoto.PayloadType, signer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to sign statement: %w", err)
|
||||
}
|
||||
mediaType, err := attestation.DSSEMediaType(vsaStatement.PredicateType)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get DSSE media type: %w", err)
|
||||
}
|
||||
|
||||
data, err := json.Marshal(env)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal envelope: %w", err)
|
||||
}
|
||||
mt := types.MediaType(mediaType)
|
||||
newLayer := static.NewLayer(data, mt)
|
||||
ann := make(map[string]string)
|
||||
ann[InTotoReferenceLifecycleStage] = LifecycleStageExperimental
|
||||
ann[oci.InTotoPredicateType] = attestation.VSAPredicateType
|
||||
withAnnotations := mutate.Addendum{
|
||||
Layer: newLayer,
|
||||
Annotations: ann,
|
||||
}
|
||||
opts = &SigningOptions{
|
||||
Replace: false,
|
||||
}
|
||||
return addSignedLayers([]mutate.Addendum{withAnnotations}, layers, outerMediaType, image, opts)
|
||||
}
|
||||
|
||||
func addSignedLayers(signedLayers []mutate.Addendum, originalLayers []v1.Layer, mediaType types.MediaType, attestationImage v1.Image, opts *SigningOptions) (v1.Image, error) {
|
||||
var err error
|
||||
if opts.Replace {
|
||||
|
||||
@@ -31,10 +31,13 @@ func VerifyAttestations(ctx context.Context, resolver oci.AttestationResolver, f
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = evaluator.Evaluate(ctx, resolver, files, input)
|
||||
rs, err := evaluator.Evaluate(ctx, resolver, files, input)
|
||||
if err != nil {
|
||||
return fmt.Errorf("policy evaluation failed: %w", err)
|
||||
}
|
||||
if !rs.Allowed() {
|
||||
return fmt.Errorf("policy evaluation failed: %s", fmt.Sprint(rs))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -8,9 +8,11 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/attest/internal/test"
|
||||
"github.com/docker/attest/pkg/attestation"
|
||||
"github.com/docker/attest/pkg/oci"
|
||||
"github.com/docker/attest/pkg/policy"
|
||||
"github.com/open-policy-agent/opa/rego"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -41,9 +43,9 @@ func TestVerifyAttestations(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
|
||||
mockPE := policy.MockPolicyEvaluator{
|
||||
EvaluateFunc: func(ctx context.Context, resolver oci.AttestationResolver, policy []*policy.PolicyFile, input *policy.PolicyInput) error {
|
||||
return tc.policyEvaluationError
|
||||
mockPE := test.MockPolicyEvaluator{
|
||||
EvaluateFunc: func(ctx context.Context, resolver oci.AttestationResolver, pfs []*policy.PolicyFile, input *policy.PolicyInput) (*rego.ResultSet, error) {
|
||||
return test.AllowedResult(), tc.policyEvaluationError
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
102
pkg/attest/vsa.go
Normal file
102
pkg/attest/vsa.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package attest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/attest/pkg/attestation"
|
||||
"github.com/docker/attest/pkg/oci"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/mutate"
|
||||
"github.com/google/go-containerregistry/pkg/v1/static"
|
||||
"github.com/google/go-containerregistry/pkg/v1/types"
|
||||
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
||||
"github.com/secure-systems-lab/go-securesystemslib/dsse"
|
||||
)
|
||||
|
||||
func generateVSA(ctx context.Context, image v1.Image, stmt []*intoto.Statement, signer dsse.SignerVerifier, opts *SigningOptions) (*mutate.Addendum, error) {
|
||||
if len(stmt) == 0 {
|
||||
return nil, fmt.Errorf("no attestations found to generate VSA from")
|
||||
}
|
||||
sub := stmt[0].Subject[0]
|
||||
stype := stmt[0].Type
|
||||
|
||||
uri, err := attestation.ToVSAResourceURI(sub)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate VSA resource URI: %w", err)
|
||||
}
|
||||
|
||||
inputs := make([]attestation.VSAInputAttestation, 0, len(stmt))
|
||||
layers, err := image.Layers()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get layers: %w", err)
|
||||
}
|
||||
for _, layer := range layers {
|
||||
mt, err := layer.MediaType()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get layer media type: %w", err)
|
||||
}
|
||||
mediaType := string(mt)
|
||||
if !strings.HasPrefix(mediaType, "application/vnd.in-toto.") ||
|
||||
!strings.HasSuffix(mediaType, "+dsse") {
|
||||
continue
|
||||
}
|
||||
|
||||
dgst, err := layer.Digest()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get layer digest: %w", err)
|
||||
}
|
||||
inputs = append(inputs, attestation.VSAInputAttestation{
|
||||
Digest: map[string]string{"sha256": dgst.Hex},
|
||||
MediaType: string(mt),
|
||||
})
|
||||
}
|
||||
vsaStatement := &intoto.Statement{
|
||||
StatementHeader: intoto.StatementHeader{
|
||||
PredicateType: attestation.VSAPredicateType,
|
||||
Type: stype,
|
||||
Subject: stmt[0].Subject,
|
||||
},
|
||||
Predicate: attestation.VSAPredicate{
|
||||
Verifier: attestation.VSAVerifier{
|
||||
ID: opts.VSAOptions.VerifierID,
|
||||
},
|
||||
TimeVerified: time.Now().UTC().Format(time.RFC3339),
|
||||
ResourceUri: uri,
|
||||
Policy: attestation.VSAPolicy{URI: opts.VSAOptions.PolicyURI},
|
||||
VerificationResult: "PASSED",
|
||||
VerifiedLevels: []string{opts.VSAOptions.BuildLevel},
|
||||
InputAttestations: inputs,
|
||||
},
|
||||
}
|
||||
payload, err := json.Marshal(vsaStatement)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal statement: %w", err)
|
||||
}
|
||||
env, err := attestation.SignDSSE(ctx, payload, intoto.PayloadType, signer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to sign statement: %w", err)
|
||||
}
|
||||
mediaType, err := attestation.DSSEMediaType(vsaStatement.PredicateType)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get DSSE media type: %w", err)
|
||||
}
|
||||
|
||||
data, err := json.Marshal(env)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal envelope: %w", err)
|
||||
}
|
||||
mt := types.MediaType(mediaType)
|
||||
newLayer := static.NewLayer(data, mt)
|
||||
ann := make(map[string]string)
|
||||
ann[InTotoReferenceLifecycleStage] = LifecycleStageExperimental
|
||||
ann[oci.InTotoPredicateType] = attestation.VSAPredicateType
|
||||
withAnnotations := mutate.Addendum{
|
||||
Layer: newLayer,
|
||||
Annotations: ann,
|
||||
}
|
||||
return &withAnnotations, nil
|
||||
}
|
||||
Reference in New Issue
Block a user