docs: update examples in README.md

This commit is contained in:
Joel Kamp
2024-05-02 13:42:35 -05:00
committed by mrjoelkamp
18 changed files with 100 additions and 77 deletions

View File

@@ -3,13 +3,13 @@ library to create, verify, and evaluate policy for attestations on container ima
# usage # usage
## verifying attestations ## verifying attestations
See example [example_verify.go](./pkg/attest/example_verify.go) See [example_verify_test.go](./pkg/attest/example_verify_test.go)
## signing attestations ## signing attestations
See example [example_sign.go](./pkg/attest/example_sign.go) See [example_sign_test.go](./pkg/attest/example_sign_test.go)
## mirroring TUF repositories to OCI ## mirroring TUF repositories to OCI
See example [example_mirror.go](./pkg/mirror/example_mirror.go) See [example_mirror_test.go](./pkg/mirror/example_mirror_test.go)
### using `go-tuf` OCI registry client ### using `go-tuf` OCI registry client
See example [example_registry](./pkg/tuf/example_registry.go) See [example_registry_test.go](./pkg/tuf/example_registry_test.go)

View File

@@ -10,7 +10,6 @@ import (
"testing" "testing"
"github.com/docker/attest/pkg/attestation" "github.com/docker/attest/pkg/attestation"
"github.com/docker/attest/pkg/oci"
"github.com/docker/attest/pkg/policy" "github.com/docker/attest/pkg/policy"
"github.com/docker/attest/pkg/signerverifier" "github.com/docker/attest/pkg/signerverifier"
"github.com/docker/attest/pkg/tlog" "github.com/docker/attest/pkg/tlog"
@@ -58,7 +57,7 @@ func Setup(t *testing.T) (context.Context, dsse.SignerVerifier) {
var policyEvaluator policy.PolicyEvaluator var policyEvaluator policy.PolicyEvaluator
if USE_MOCK_POLICY { if USE_MOCK_POLICY {
policyEvaluator = GetMockPolicy() policyEvaluator = policy.GetMockPolicy()
} else { } else {
policyEvaluator = policy.NewRegoEvaluator(true) policyEvaluator = policy.NewRegoEvaluator(true)
} }
@@ -86,25 +85,6 @@ func GetMockSigner(ctx context.Context) (dsse.SignerVerifier, error) {
return signerverifier.GenKeyPair() return signerverifier.GenKeyPair()
} }
type MockPolicyEvaluator struct {
EvaluateFunc func(ctx context.Context, resolver oci.AttestationResolver, policy []*policy.PolicyFile, input *policy.PolicyInput) error
}
func (pe *MockPolicyEvaluator) Evaluate(ctx context.Context, resolver oci.AttestationResolver, policy []*policy.PolicyFile, input *policy.PolicyInput) error {
if pe.EvaluateFunc != nil {
return pe.EvaluateFunc(ctx, resolver, policy, input)
}
return nil
}
func GetMockPolicy() policy.PolicyEvaluator {
return &MockPolicyEvaluator{
EvaluateFunc: func(ctx context.Context, resolver oci.AttestationResolver, policy []*policy.PolicyFile, input *policy.PolicyInput) error {
return nil
},
}
}
type AnnotatedStatement struct { type AnnotatedStatement struct {
OCIDescriptor *v1.Descriptor OCIDescriptor *v1.Descriptor
InTotoStatement *intoto.Statement InTotoStatement *intoto.Statement

View File

@@ -1,8 +1,9 @@
package attest package attest_test
import ( import (
"context" "context"
"github.com/docker/attest/pkg/attest"
"github.com/docker/attest/pkg/attestation" "github.com/docker/attest/pkg/attestation"
"github.com/docker/attest/pkg/mirror" "github.com/docker/attest/pkg/mirror"
"github.com/docker/attest/pkg/oci" "github.com/docker/attest/pkg/oci"
@@ -25,7 +26,7 @@ func ExampleSign_remote() {
// signer, err := signerverifier.GetAWSSigner(cmd.Context(), aws_arn, aws_region) // signer, err := signerverifier.GetAWSSigner(cmd.Context(), aws_arn, aws_region)
// configure signing options // configure signing options
opts := &SigningOptions{ opts := &attest.SigningOptions{
Replace: true, // replace unsigned intoto statements with signed intoto attestations, otherwise leave in place Replace: true, // replace unsigned intoto statements with signed intoto attestations, otherwise leave in place
} }
@@ -50,7 +51,7 @@ func ExampleSign_remote() {
// att, err := oci.AttestationIndexFromLocal(path) // att, err := oci.AttestationIndexFromLocal(path)
// sign attestations // sign attestations
signedImageIndex, err := Sign(context.Background(), att.Index, signer, opts) signedImageIndex, err := attest.Sign(context.Background(), att.Index, signer, opts)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -1,4 +1,4 @@
package attest package attest_test
import ( import (
"context" "context"
@@ -6,6 +6,7 @@ import (
"path/filepath" "path/filepath"
"github.com/docker/attest/internal/embed" "github.com/docker/attest/internal/embed"
"github.com/docker/attest/pkg/attest"
"github.com/docker/attest/pkg/oci" "github.com/docker/attest/pkg/oci"
"github.com/docker/attest/pkg/policy" "github.com/docker/attest/pkg/policy"
"github.com/docker/attest/pkg/tuf" "github.com/docker/attest/pkg/tuf"
@@ -19,7 +20,7 @@ func createTufClient(outputPath string) (*tuf.TufClient, error) {
// metadataURI := "https://docker.github.io/tuf-staging/metadata" // metadataURI := "https://docker.github.io/tuf-staging/metadata"
// targetsURI := "https://docker.github.io/tuf-staging/targets" // targetsURI := "https://docker.github.io/tuf-staging/targets"
return tuf.NewTufClient(embed.DefaultRoot, outputPath, metadataURI, targetsURI) return tuf.NewTufClient(embed.StagingRoot, outputPath, metadataURI, targetsURI)
} }
func ExampleVerify_remote() { func ExampleVerify_remote() {
@@ -57,7 +58,7 @@ func ExampleVerify_remote() {
} }
// verify attestations // verify attestations
policy, err := Verify(context.Background(), opts, resolver) policy, err := attest.Verify(context.Background(), opts, resolver)
if err != nil { if err != nil {
panic(err) // failed policy or attestation signature verification panic(err) // failed policy or attestation signature verification
} }

View File

@@ -31,10 +31,13 @@ func VerifyAttestations(ctx context.Context, resolver oci.AttestationResolver, f
if err != nil { if err != nil {
return err return err
} }
err = evaluator.Evaluate(ctx, resolver, files, input) rs, err := evaluator.Evaluate(ctx, resolver, files, input)
if err != nil { if err != nil {
return fmt.Errorf("policy evaluation failed: %w", err) return fmt.Errorf("policy evaluation failed: %w", err)
} }
if !rs.Allowed() {
return fmt.Errorf("policy evaluation failed: %s", fmt.Sprint(rs))
}
return nil return nil
} }

View File

@@ -8,10 +8,10 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
"github.com/docker/attest/internal/test"
"github.com/docker/attest/pkg/attestation" "github.com/docker/attest/pkg/attestation"
"github.com/docker/attest/pkg/oci" "github.com/docker/attest/pkg/oci"
"github.com/docker/attest/pkg/policy" "github.com/docker/attest/pkg/policy"
"github.com/open-policy-agent/opa/rego"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -42,9 +42,9 @@ func TestVerifyAttestations(t *testing.T) {
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
mockPE := test.MockPolicyEvaluator{ mockPE := policy.MockPolicyEvaluator{
EvaluateFunc: func(ctx context.Context, resolver oci.AttestationResolver, pfs []*policy.PolicyFile, input *policy.PolicyInput) error { EvaluateFunc: func(ctx context.Context, resolver oci.AttestationResolver, pfs []*policy.PolicyFile, input *policy.PolicyInput) (*rego.ResultSet, error) {
return tc.policyEvaluationError return policy.AllowedResult(), tc.policyEvaluationError
}, },
} }

View File

@@ -1,4 +1,4 @@
package mirror package mirror_test
import ( import (
"fmt" "fmt"
@@ -7,17 +7,18 @@ import (
"strings" "strings"
"github.com/docker/attest/internal/embed" "github.com/docker/attest/internal/embed"
"github.com/docker/attest/pkg/mirror"
v1 "github.com/google/go-containerregistry/pkg/v1" v1 "github.com/google/go-containerregistry/pkg/v1"
) )
type TufMirrorOutput struct { type TufMirrorOutput struct {
metadata *v1.Image metadata *v1.Image
delegatedMetadata []*MirrorImage delegatedMetadata []*mirror.MirrorImage
targets []*MirrorImage targets []*mirror.MirrorImage
delegatedTargets []*MirrorIndex delegatedTargets []*mirror.MirrorIndex
} }
func ExampleMirror() { func ExampleNewTufMirror() {
home, err := os.UserHomeDir() home, err := os.UserHomeDir()
if err != nil { if err != nil {
panic(err) panic(err)
@@ -27,7 +28,7 @@ func ExampleMirror() {
// configure TUF mirror // configure TUF mirror
metadataURI := "https://docker.github.io/tuf-staging/metadata" metadataURI := "https://docker.github.io/tuf-staging/metadata"
targetsURI := "https://docker.github.io/tuf-staging/targets" targetsURI := "https://docker.github.io/tuf-staging/targets"
m, err := NewTufMirror(embed.DefaultRoot, tufOutputPath, metadataURI, targetsURI) m, err := mirror.NewTufMirror(embed.StagingRoot, tufOutputPath, metadataURI, targetsURI)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -78,7 +79,7 @@ func ExampleMirror() {
func mirrorToRegistry(o *TufMirrorOutput) error { func mirrorToRegistry(o *TufMirrorOutput) error {
// push metadata to registry // push metadata to registry
metadataRepo := "registry-1.docker.io/docker/tuf-metadata:latest" metadataRepo := "registry-1.docker.io/docker/tuf-metadata:latest"
err := PushToRegistry(o.metadata, metadataRepo) err := mirror.PushToRegistry(o.metadata, metadataRepo)
if err != nil { if err != nil {
return err return err
} }
@@ -89,7 +90,7 @@ func mirrorToRegistry(o *TufMirrorOutput) error {
return fmt.Errorf("failed to get repo without tag: %s", metadataRepo) return fmt.Errorf("failed to get repo without tag: %s", metadataRepo)
} }
imageName := fmt.Sprintf("%s:%s", repo, metadata.Tag) imageName := fmt.Sprintf("%s:%s", repo, metadata.Tag)
err = PushToRegistry(metadata.Image, imageName) err = mirror.PushToRegistry(metadata.Image, imageName)
if err != nil { if err != nil {
return err return err
} }
@@ -99,7 +100,7 @@ func mirrorToRegistry(o *TufMirrorOutput) error {
targetsRepo := "registry-1.docker.io/docker/tuf-targets" targetsRepo := "registry-1.docker.io/docker/tuf-targets"
for _, target := range o.targets { for _, target := range o.targets {
imageName := fmt.Sprintf("%s:%s", targetsRepo, target.Tag) imageName := fmt.Sprintf("%s:%s", targetsRepo, target.Tag)
err = PushToRegistry(target.Image, imageName) err = mirror.PushToRegistry(target.Image, imageName)
if err != nil { if err != nil {
return err return err
} }
@@ -107,7 +108,7 @@ func mirrorToRegistry(o *TufMirrorOutput) error {
// push delegated targets to registry // push delegated targets to registry
for _, target := range o.delegatedTargets { for _, target := range o.delegatedTargets {
imageName := fmt.Sprintf("%s:%s", targetsRepo, target.Tag) imageName := fmt.Sprintf("%s:%s", targetsRepo, target.Tag)
err = PushToRegistry(target.Index, imageName) err = mirror.PushToRegistry(target.Index, imageName)
if err != nil { if err != nil {
return err return err
} }
@@ -117,14 +118,14 @@ func mirrorToRegistry(o *TufMirrorOutput) error {
func mirrorToLocal(o *TufMirrorOutput, outputPath string) error { func mirrorToLocal(o *TufMirrorOutput, outputPath string) error {
// output metadata to local directory // output metadata to local directory
err := SaveAsOCILayout(o.metadata, outputPath) err := mirror.SaveAsOCILayout(o.metadata, outputPath)
if err != nil { if err != nil {
return err return err
} }
// output delegated metadata to local directory // output delegated metadata to local directory
for _, metadata := range o.delegatedMetadata { for _, metadata := range o.delegatedMetadata {
path := filepath.Join(outputPath, metadata.Tag) path := filepath.Join(outputPath, metadata.Tag)
err = SaveAsOCILayout(metadata.Image, path) err = mirror.SaveAsOCILayout(metadata.Image, path)
if err != nil { if err != nil {
return err return err
} }
@@ -133,7 +134,7 @@ func mirrorToLocal(o *TufMirrorOutput, outputPath string) error {
// output top-level targets to local directory // output top-level targets to local directory
for _, target := range o.targets { for _, target := range o.targets {
path := filepath.Join(outputPath, target.Tag) path := filepath.Join(outputPath, target.Tag)
err = SaveAsOCILayout(target.Image, path) err = mirror.SaveAsOCILayout(target.Image, path)
if err != nil { if err != nil {
return err return err
} }
@@ -141,7 +142,7 @@ func mirrorToLocal(o *TufMirrorOutput, outputPath string) error {
// output delegated targets to local directory // output delegated targets to local directory
for _, target := range o.delegatedTargets { for _, target := range o.delegatedTargets {
path := filepath.Join(outputPath, target.Tag) path := filepath.Join(outputPath, target.Tag)
err = SaveAsOCILayout(target.Index, path) err = mirror.SaveAsOCILayout(target.Index, path)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"github.com/docker/attest/pkg/oci" "github.com/docker/attest/pkg/oci"
"github.com/open-policy-agent/opa/rego"
) )
type policyEvaluatorCtxKeyType struct{} type policyEvaluatorCtxKeyType struct{}
@@ -26,5 +27,5 @@ func GetPolicyEvaluator(ctx context.Context) (PolicyEvaluator, error) {
} }
type PolicyEvaluator interface { type PolicyEvaluator interface {
Evaluate(ctx context.Context, resolver oci.AttestationResolver, policy []*PolicyFile, input *PolicyInput) error Evaluate(ctx context.Context, resolver oci.AttestationResolver, policy []*PolicyFile, input *PolicyInput) (*rego.ResultSet, error)
} }

40
pkg/policy/mock.go Normal file
View File

@@ -0,0 +1,40 @@
package policy
import (
"context"
"github.com/docker/attest/pkg/oci"
"github.com/open-policy-agent/opa/rego"
)
type MockPolicyEvaluator struct {
EvaluateFunc func(ctx context.Context, resolver oci.AttestationResolver, policy []*PolicyFile, input *PolicyInput) (*rego.ResultSet, error)
}
func (pe *MockPolicyEvaluator) Evaluate(ctx context.Context, resolver oci.AttestationResolver, policy []*PolicyFile, input *PolicyInput) (*rego.ResultSet, error) {
if pe.EvaluateFunc != nil {
return pe.EvaluateFunc(ctx, resolver, policy, input)
}
return AllowedResult(), nil
}
func GetMockPolicy() PolicyEvaluator {
return &MockPolicyEvaluator{
EvaluateFunc: func(ctx context.Context, resolver oci.AttestationResolver, pfs []*PolicyFile, input *PolicyInput) (*rego.ResultSet, error) {
return AllowedResult(), nil
},
}
}
func AllowedResult() *rego.ResultSet {
return &rego.ResultSet{
{
Bindings: rego.Vars{},
Expressions: []*rego.ExpressionValue{
{
Value: true,
},
},
},
}
}

View File

@@ -97,11 +97,13 @@ func TestRegoEvaluator_Evaluate(t *testing.T) {
policyFiles, err := policy.ResolvePolicy(ctx, tc.resolver, tc.policy) policyFiles, err := policy.ResolvePolicy(ctx, tc.resolver, tc.policy)
assert.NoErrorf(t, err, "failed to resolve policy") assert.NoErrorf(t, err, "failed to resolve policy")
err = re.Evaluate(ctx, tc.resolver, policyFiles, tc.input) rs, err := re.Evaluate(ctx, tc.resolver, policyFiles, tc.input)
if tc.expectSuccess { if tc.expectSuccess {
assert.NoErrorf(t, err, "Evaluate failed") assert.NoErrorf(t, err, "Evaluate failed")
assert.True(t, rs.Allowed(), "Evaluate should have succeeded")
} else { } else {
assert.Errorf(t, err, "Evaluate should have failed") assert.False(t, rs.Allowed(), "Evaluate should have failed")
} }
}) })
} }

View File

@@ -29,10 +29,11 @@ type regoEvaluator struct {
func NewRegoEvaluator(debug bool) PolicyEvaluator { func NewRegoEvaluator(debug bool) PolicyEvaluator {
return &regoEvaluator{ return &regoEvaluator{
debug: debug, debug: debug,
query: "data.attest.allow",
} }
} }
func (re *regoEvaluator) Evaluate(ctx context.Context, resolver oci.AttestationResolver, files []*PolicyFile, input *PolicyInput) error { func (re *regoEvaluator) Evaluate(ctx context.Context, resolver oci.AttestationResolver, files []*PolicyFile, input *PolicyInput) (*rego.ResultSet, error) {
var regoOpts []func(*rego.Rego) var regoOpts []func(*rego.Rego)
// Create a new in-memory store // Create a new in-memory store
@@ -41,7 +42,7 @@ func (re *regoEvaluator) Evaluate(ctx context.Context, resolver oci.AttestationR
params.Write = true params.Write = true
txn, err := store.NewTransaction(ctx, params) txn, err := store.NewTransaction(ctx, params)
if err != nil { if err != nil {
return err return nil, err
} }
for _, target := range files { for _, target := range files {
@@ -49,11 +50,11 @@ func (re *regoEvaluator) Evaluate(ctx context.Context, resolver oci.AttestationR
if filepath.Ext(target.Path) == ".yaml" { if filepath.Ext(target.Path) == ".yaml" {
yamlData, err := loadYAML(target.Path, target.Content) yamlData, err := loadYAML(target.Path, target.Content)
if err != nil { if err != nil {
return err return nil, err
} }
err = store.Write(ctx, txn, storage.AddOp, storage.Path{}, yamlData) err = store.Write(ctx, txn, storage.AddOp, storage.Path{}, yamlData)
if err != nil { if err != nil {
return err return nil, err
} }
} else { } else {
regoOpts = append(regoOpts, rego.Module(target.Path, string(target.Content))) regoOpts = append(regoOpts, rego.Module(target.Path, string(target.Content)))
@@ -63,7 +64,7 @@ func (re *regoEvaluator) Evaluate(ctx context.Context, resolver oci.AttestationR
err = store.Commit(ctx, txn) err = store.Commit(ctx, txn)
if err != nil { if err != nil {
store.Abort(ctx, txn) store.Abort(ctx, txn)
return err return nil, err
} }
if re.debug { if re.debug {
@@ -75,7 +76,7 @@ func (re *regoEvaluator) Evaluate(ctx context.Context, resolver oci.AttestationR
} }
regoOpts = append(regoOpts, regoOpts = append(regoOpts,
rego.Query("data.docker.allow"), rego.Query(re.query),
rego.StrictBuiltinErrors(true), rego.StrictBuiltinErrors(true),
rego.Input(input), rego.Input(input),
rego.Store(store), rego.Store(store),
@@ -86,15 +87,7 @@ func (re *regoEvaluator) Evaluate(ctx context.Context, resolver oci.AttestationR
r := rego.New(regoOpts...) r := rego.New(regoOpts...)
rs, err := r.Eval(ctx) rs, err := r.Eval(ctx)
if err != nil { return &rs, err
return fmt.Errorf("error from Eval: %w", err)
}
if !rs.Allowed() {
return fmt.Errorf("policy evaluation failed")
}
return nil
} }
var dynamicObj = types.NewObject(nil, types.NewDynamicProperty(types.S, types.A)) var dynamicObj = types.NewObject(nil, types.NewDynamicProperty(types.S, types.A))

View File

@@ -1,4 +1,4 @@
package docker package attest
import rego.v1 import rego.v1

View File

@@ -1,4 +1,4 @@
package docker package attest
import rego.v1 import rego.v1

View File

@@ -1,4 +1,4 @@
package docker package attest
import rego.v1 import rego.v1

View File

@@ -1,4 +1,4 @@
package docker package attest
import rego.v1 import rego.v1

View File

@@ -1,14 +1,15 @@
package tuf package tuf_test
import ( import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/docker/attest/internal/embed" "github.com/docker/attest/internal/embed"
"github.com/docker/attest/pkg/tuf"
"github.com/theupdateframework/go-tuf/v2/metadata" "github.com/theupdateframework/go-tuf/v2/metadata"
) )
func ExampleTufRegistryClient() { func ExampleNewTufClient_registry() {
// create a tuf client // create a tuf client
home, err := os.UserHomeDir() home, err := os.UserHomeDir()
if err != nil { if err != nil {
@@ -19,7 +20,7 @@ func ExampleTufRegistryClient() {
// using oci tuf metadata and targets // using oci tuf metadata and targets
metadataURI := "regsitry-1.docker.io/docker/tuf-metadata:latest" metadataURI := "regsitry-1.docker.io/docker/tuf-metadata:latest"
targetsURI := "regsitry-1.docker.io/docker/tuf-targets" targetsURI := "regsitry-1.docker.io/docker/tuf-targets"
registryClient, err := NewTufClient(embed.DefaultRoot, tufOutputPath, metadataURI, targetsURI) registryClient, err := tuf.NewTufClient(embed.StagingRoot, tufOutputPath, metadataURI, targetsURI)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -1,4 +1,4 @@
package docker package attest
import rego.v1 import rego.v1

View File

@@ -1,4 +1,4 @@
package docker package attest
import rego.v1 import rego.v1
config := {"keys": []} config := {"keys": []}