feat!: remove MockTUFClient (#135)
* feat! remove MockTUFClient *Breaking* - use LocalPolicyDir and nil TUFClient instead Other: - add stateful Verifier
This commit is contained in:
@@ -31,6 +31,7 @@ func ExampleVerify_remote() {
|
|||||||
LocalTargetsDir: filepath.Join(home, ".docker", "policy"), // location to store policy files downloaded from TUF
|
LocalTargetsDir: filepath.Join(home, ".docker", "policy"), // location to store policy files downloaded from TUF
|
||||||
LocalPolicyDir: "", // overrides TUF policy for local policy files if set
|
LocalPolicyDir: "", // overrides TUF policy for local policy files if set
|
||||||
PolicyID: "", // set to ignore policy mapping and select a policy by id
|
PolicyID: "", // set to ignore policy mapping and select a policy by id
|
||||||
|
DisableTUF: false, // set to disable TUF and rely on local policy files
|
||||||
}
|
}
|
||||||
|
|
||||||
src, err := oci.ParseImageSpec(image, oci.WithPlatform(platform))
|
src, err := oci.ParseImageSpec(image, oci.WithPlatform(platform))
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"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/docker/attest/pkg/tuf"
|
|
||||||
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
||||||
v02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2"
|
v02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -28,7 +27,6 @@ var (
|
|||||||
|
|
||||||
func TestSignVerifyOCILayout(t *testing.T) {
|
func TestSignVerifyOCILayout(t *testing.T) {
|
||||||
ctx, signer := test.Setup(t)
|
ctx, signer := test.Setup(t)
|
||||||
ctx = tuf.WithDownloader(ctx, tuf.NewMockTufClient(EmptyPolicyDir, test.CreateTempDir(t, "", "tuf-dest")))
|
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -45,6 +43,7 @@ func TestSignVerifyOCILayout(t *testing.T) {
|
|||||||
}
|
}
|
||||||
policyOpts := &policy.Options{
|
policyOpts := &policy.Options{
|
||||||
LocalPolicyDir: PassPolicyDir,
|
LocalPolicyDir: PassPolicyDir,
|
||||||
|
DisableTUF: true,
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
|||||||
@@ -17,26 +17,41 @@ import (
|
|||||||
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
intoto "github.com/in-toto/in-toto-golang/in_toto"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Verify(ctx context.Context, src *oci.ImageSpec, opts *policy.Options) (result *VerificationResult, err error) {
|
type Verifier interface {
|
||||||
// so that we can resolve mapping from the image name earlier
|
Verify(ctx context.Context, src *oci.ImageSpec) (result *VerificationResult, err error)
|
||||||
detailsResolver, err := policy.CreateImageDetailsResolver(src)
|
}
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create image details resolver: %w", err)
|
type tufVerifier struct {
|
||||||
}
|
opts *policy.Options
|
||||||
err = populateDefaultOptions(opts)
|
tufClient tuf.Downloader
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVerifier(opts *policy.Options) (Verifier, error) {
|
||||||
|
err := populateDefaultOptions(opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
var tufClient tuf.Downloader
|
||||||
tufClient, ok := tuf.GetDownloader(ctx)
|
if !opts.DisableTUF {
|
||||||
if !ok {
|
|
||||||
tufClient, err = tuf.NewClient(opts.TUFClientOptions)
|
tufClient, err = tuf.NewClient(opts.TUFClientOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create TUF client: %w", err)
|
return nil, fmt.Errorf("failed to create TUF client: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return &tufVerifier{
|
||||||
|
opts: opts,
|
||||||
|
tufClient: tufClient,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
pctx, err := policy.ResolvePolicy(ctx, tufClient, detailsResolver, opts)
|
func (v *tufVerifier) Verify(ctx context.Context, src *oci.ImageSpec) (result *VerificationResult, err error) {
|
||||||
|
// so that we can resolve mapping from the image name earlier
|
||||||
|
detailsResolver, err := policy.CreateImageDetailsResolver(src)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create image details resolver: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pctx, err := policy.ResolvePolicy(ctx, v.tufClient, detailsResolver, v.opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to resolve policy: %w", err)
|
return nil, fmt.Errorf("failed to resolve policy: %w", err)
|
||||||
}
|
}
|
||||||
@@ -47,14 +62,14 @@ func Verify(ctx context.Context, src *oci.ImageSpec, opts *policy.Options) (resu
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
// this is overriding the mapping with a referrers config. Useful for testing if nothing else
|
// this is overriding the mapping with a referrers config. Useful for testing if nothing else
|
||||||
if opts.ReferrersRepo != "" {
|
if v.opts.ReferrersRepo != "" {
|
||||||
pctx.Mapping.Attestations = &config.AttestationConfig{
|
pctx.Mapping.Attestations = &config.AttestationConfig{
|
||||||
Repo: opts.ReferrersRepo,
|
Repo: v.opts.ReferrersRepo,
|
||||||
Style: config.AttestationStyleReferrers,
|
Style: config.AttestationStyleReferrers,
|
||||||
}
|
}
|
||||||
} else if opts.AttestationStyle == config.AttestationStyleAttached {
|
} else if v.opts.AttestationStyle == config.AttestationStyleAttached {
|
||||||
pctx.Mapping.Attestations = &config.AttestationConfig{
|
pctx.Mapping.Attestations = &config.AttestationConfig{
|
||||||
Repo: opts.ReferrersRepo,
|
Repo: v.opts.ReferrersRepo,
|
||||||
Style: config.AttestationStyleAttached,
|
Style: config.AttestationStyleAttached,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,15 +85,27 @@ func Verify(ctx context.Context, src *oci.ImageSpec, opts *policy.Options) (resu
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Verify(ctx context.Context, src *oci.ImageSpec, opts *policy.Options) (result *VerificationResult, err error) {
|
||||||
|
verifier, err := NewVerifier(opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return verifier.Verify(ctx, src)
|
||||||
|
}
|
||||||
|
|
||||||
func populateDefaultOptions(opts *policy.Options) (err error) {
|
func populateDefaultOptions(opts *policy.Options) (err error) {
|
||||||
|
if opts.LocalPolicyDir == "" && opts.DisableTUF {
|
||||||
|
return fmt.Errorf("local policy dir must be set if not using TUF")
|
||||||
|
}
|
||||||
if opts.LocalTargetsDir == "" {
|
if opts.LocalTargetsDir == "" {
|
||||||
opts.LocalTargetsDir, err = defaultLocalTargetsDir()
|
opts.LocalTargetsDir, err = defaultLocalTargetsDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if opts.DisableTUF && opts.TUFClientOptions != nil {
|
||||||
if opts.TUFClientOptions == nil {
|
return fmt.Errorf("TUF client options set but TUF disabled")
|
||||||
|
} else if opts.TUFClientOptions == nil && !opts.DisableTUF {
|
||||||
opts.TUFClientOptions = tuf.NewDockerDefaultClientOptions(opts.LocalTargetsDir)
|
opts.TUFClientOptions = tuf.NewDockerDefaultClientOptions(opts.LocalTargetsDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +115,6 @@ func populateDefaultOptions(opts *policy.Options) (err error) {
|
|||||||
if opts.ReferrersRepo != "" && opts.AttestationStyle != config.AttestationStyleReferrers {
|
if opts.ReferrersRepo != "" && opts.AttestationStyle != config.AttestationStyleReferrers {
|
||||||
return fmt.Errorf("referrers repo specified but attestation source not set to referrers")
|
return fmt.Errorf("referrers repo specified but attestation source not set to referrers")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,6 @@ func TestVerifyAttestations(t *testing.T) {
|
|||||||
func TestVSA(t *testing.T) {
|
func TestVSA(t *testing.T) {
|
||||||
ctx, signer := test.Setup(t)
|
ctx, signer := test.Setup(t)
|
||||||
ctx = policy.WithPolicyEvaluator(ctx, policy.NewRegoEvaluator(true))
|
ctx = policy.WithPolicyEvaluator(ctx, policy.NewRegoEvaluator(true))
|
||||||
ctx = tuf.WithDownloader(ctx, tuf.NewMockTufClient(EmptyPolicyDir, test.CreateTempDir(t, "", "tuf-dest")))
|
|
||||||
// setup an image with signed attestations
|
// setup an image with signed attestations
|
||||||
outputLayout := test.CreateTempDir(t, "", TestTempDir)
|
outputLayout := test.CreateTempDir(t, "", TestTempDir)
|
||||||
|
|
||||||
@@ -93,6 +92,7 @@ func TestVSA(t *testing.T) {
|
|||||||
policyOpts := &policy.Options{
|
policyOpts := &policy.Options{
|
||||||
LocalPolicyDir: PassPolicyDir,
|
LocalPolicyDir: PassPolicyDir,
|
||||||
AttestationStyle: config.AttestationStyleAttached,
|
AttestationStyle: config.AttestationStyleAttached,
|
||||||
|
DisableTUF: true,
|
||||||
}
|
}
|
||||||
results, err := Verify(ctx, spec, policyOpts)
|
results, err := Verify(ctx, spec, policyOpts)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -123,7 +123,6 @@ func TestVSA(t *testing.T) {
|
|||||||
func TestVerificationFailure(t *testing.T) {
|
func TestVerificationFailure(t *testing.T) {
|
||||||
ctx, signer := test.Setup(t)
|
ctx, signer := test.Setup(t)
|
||||||
ctx = policy.WithPolicyEvaluator(ctx, policy.NewRegoEvaluator(true))
|
ctx = policy.WithPolicyEvaluator(ctx, policy.NewRegoEvaluator(true))
|
||||||
ctx = tuf.WithDownloader(ctx, tuf.NewMockTufClient(EmptyPolicyDir, test.CreateTempDir(t, "", "tuf-dest")))
|
|
||||||
// setup an image with signed attestations
|
// setup an image with signed attestations
|
||||||
outputLayout := test.CreateTempDir(t, "", TestTempDir)
|
outputLayout := test.CreateTempDir(t, "", TestTempDir)
|
||||||
|
|
||||||
@@ -146,6 +145,7 @@ func TestVerificationFailure(t *testing.T) {
|
|||||||
policyOpts := &policy.Options{
|
policyOpts := &policy.Options{
|
||||||
LocalPolicyDir: FailPolicyDir,
|
LocalPolicyDir: FailPolicyDir,
|
||||||
AttestationStyle: config.AttestationStyleAttached,
|
AttestationStyle: config.AttestationStyleAttached,
|
||||||
|
DisableTUF: true,
|
||||||
}
|
}
|
||||||
results, err := Verify(ctx, spec, policyOpts)
|
results, err := Verify(ctx, spec, policyOpts)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -176,16 +176,14 @@ func TestVerificationFailure(t *testing.T) {
|
|||||||
func TestSignVerify(t *testing.T) {
|
func TestSignVerify(t *testing.T) {
|
||||||
ctx, signer := test.Setup(t)
|
ctx, signer := test.Setup(t)
|
||||||
ctx = policy.WithPolicyEvaluator(ctx, policy.NewRegoEvaluator(true))
|
ctx = policy.WithPolicyEvaluator(ctx, policy.NewRegoEvaluator(true))
|
||||||
ctx = tuf.WithDownloader(ctx, tuf.NewMockTufClient(EmptyPolicyDir, test.CreateTempDir(t, "", "tuf-dest")))
|
|
||||||
// setup an image with signed attestations
|
// setup an image with signed attestations
|
||||||
outputLayout := test.CreateTempDir(t, "", TestTempDir)
|
outputLayout := test.CreateTempDir(t, "", TestTempDir)
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
signTL bool
|
signTL bool
|
||||||
policyDir string
|
policyDir string
|
||||||
imageName string
|
imageName string
|
||||||
|
|
||||||
expectedNonSuccess Outcome
|
expectedNonSuccess Outcome
|
||||||
}{
|
}{
|
||||||
{name: "happy path", signTL: true, policyDir: PassNoTLPolicyDir},
|
{name: "happy path", signTL: true, policyDir: PassNoTLPolicyDir},
|
||||||
@@ -223,6 +221,7 @@ func TestSignVerify(t *testing.T) {
|
|||||||
|
|
||||||
policyOpts := &policy.Options{
|
policyOpts := &policy.Options{
|
||||||
LocalPolicyDir: tc.policyDir,
|
LocalPolicyDir: tc.policyDir,
|
||||||
|
DisableTUF: true,
|
||||||
}
|
}
|
||||||
results, err := Verify(ctx, spec, policyOpts)
|
results, err := Verify(ctx, spec, policyOpts)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -251,6 +250,8 @@ func TestDefaultOptions(t *testing.T) {
|
|||||||
attestationStyle config.AttestationStyle
|
attestationStyle config.AttestationStyle
|
||||||
referrersRepo string
|
referrersRepo string
|
||||||
expectedError string
|
expectedError string
|
||||||
|
disableTuf bool
|
||||||
|
localPolicyDir string
|
||||||
}{
|
}{
|
||||||
{name: "empty"},
|
{name: "empty"},
|
||||||
{name: "tufClient provided", tufOpts: &tuf.ClientOptions{MetadataSource: "a", TargetsSource: "b"}},
|
{name: "tufClient provided", tufOpts: &tuf.ClientOptions{MetadataSource: "a", TargetsSource: "b"}},
|
||||||
@@ -258,6 +259,8 @@ func TestDefaultOptions(t *testing.T) {
|
|||||||
{name: "attestationStyle provided", attestationStyle: config.AttestationStyleAttached},
|
{name: "attestationStyle provided", attestationStyle: config.AttestationStyleAttached},
|
||||||
{name: "referrersRepo provided", referrersRepo: "referrers"},
|
{name: "referrersRepo provided", referrersRepo: "referrers"},
|
||||||
{name: "referrersRepo provided with attached", referrersRepo: "referrers", attestationStyle: config.AttestationStyleAttached, expectedError: "referrers repo specified but attestation source not set to referrers"},
|
{name: "referrersRepo provided with attached", referrersRepo: "referrers", attestationStyle: config.AttestationStyleAttached, expectedError: "referrers repo specified but attestation source not set to referrers"},
|
||||||
|
{name: "tuf disabled and no local-policy-dir", disableTuf: true, expectedError: "local policy dir must be set if not using TUF"},
|
||||||
|
{name: "tuf disabled but options set", disableTuf: true, tufOpts: &tuf.ClientOptions{MetadataSource: "a", TargetsSource: "b"}, localPolicyDir: "foo", expectedError: "TUF client options set but TUF disabled"},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
@@ -269,6 +272,8 @@ func TestDefaultOptions(t *testing.T) {
|
|||||||
LocalTargetsDir: tc.localTargetsDir,
|
LocalTargetsDir: tc.localTargetsDir,
|
||||||
AttestationStyle: tc.attestationStyle,
|
AttestationStyle: tc.attestationStyle,
|
||||||
ReferrersRepo: tc.referrersRepo,
|
ReferrersRepo: tc.referrersRepo,
|
||||||
|
DisableTUF: tc.disableTuf,
|
||||||
|
LocalPolicyDir: tc.localPolicyDir,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = populateDefaultOptions(opts)
|
err = populateDefaultOptions(opts)
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import (
|
|||||||
"github.com/docker/attest/pkg/config"
|
"github.com/docker/attest/pkg/config"
|
||||||
"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/google/go-containerregistry/pkg/name"
|
"github.com/google/go-containerregistry/pkg/name"
|
||||||
"github.com/google/go-containerregistry/pkg/registry"
|
"github.com/google/go-containerregistry/pkg/registry"
|
||||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||||
@@ -28,14 +27,12 @@ var (
|
|||||||
LocalPolicyAttached = filepath.Join("..", "..", "test", "testdata", "local-policy-attached")
|
LocalPolicyAttached = filepath.Join("..", "..", "test", "testdata", "local-policy-attached")
|
||||||
PassNoTLPolicyDir = filepath.Join("..", "..", "test", "testdata", "local-policy-no-tl")
|
PassNoTLPolicyDir = filepath.Join("..", "..", "test", "testdata", "local-policy-no-tl")
|
||||||
FailPolicyDir = filepath.Join("..", "..", "test", "testdata", "local-policy-fail")
|
FailPolicyDir = filepath.Join("..", "..", "test", "testdata", "local-policy-fail")
|
||||||
EmptyTUFDir = filepath.Join("..", "..", "test", "testdata", "local-policy-no-policies")
|
|
||||||
TestTempDir = "attest-sign-test"
|
TestTempDir = "attest-sign-test"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAttestationReferenceTypes(t *testing.T) {
|
func TestAttestationReferenceTypes(t *testing.T) {
|
||||||
ctx, signer := test.Setup(t)
|
ctx, signer := test.Setup(t)
|
||||||
ctx = policy.WithPolicyEvaluator(ctx, policy.NewRegoEvaluator(true))
|
ctx = policy.WithPolicyEvaluator(ctx, policy.NewRegoEvaluator(true))
|
||||||
ctx = tuf.WithDownloader(ctx, tuf.NewMockTufClient(EmptyTUFDir, test.CreateTempDir(t, "", "tuf-dest")))
|
|
||||||
platforms := []string{"linux/amd64", "linux/arm64"}
|
platforms := []string{"linux/amd64", "linux/arm64"}
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
name string
|
name string
|
||||||
@@ -142,6 +139,7 @@ func TestAttestationReferenceTypes(t *testing.T) {
|
|||||||
|
|
||||||
policyOpts := &policy.Options{
|
policyOpts := &policy.Options{
|
||||||
LocalPolicyDir: LocalPolicy,
|
LocalPolicyDir: LocalPolicy,
|
||||||
|
DisableTUF: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
if tc.referrersRepo != "" {
|
if tc.referrersRepo != "" {
|
||||||
@@ -185,7 +183,6 @@ func TestAttestationReferenceTypes(t *testing.T) {
|
|||||||
|
|
||||||
func TestReferencesInDifferentRepo(t *testing.T) {
|
func TestReferencesInDifferentRepo(t *testing.T) {
|
||||||
ctx, signer := test.Setup(t)
|
ctx, signer := test.Setup(t)
|
||||||
ctx = tuf.WithDownloader(ctx, tuf.NewMockTufClient(EmptyTUFDir, test.CreateTempDir(t, "", "tuf-dest")))
|
|
||||||
repoName := "repo"
|
repoName := "repo"
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
name string
|
name string
|
||||||
@@ -273,6 +270,7 @@ func TestReferencesInDifferentRepo(t *testing.T) {
|
|||||||
referencedImage := fmt.Sprintf("%s@%s", indexName, mf.Digest.String())
|
referencedImage := fmt.Sprintf("%s@%s", indexName, mf.Digest.String())
|
||||||
policyOpts := &policy.Options{
|
policyOpts := &policy.Options{
|
||||||
LocalPolicyDir: PassPolicyDir,
|
LocalPolicyDir: PassPolicyDir,
|
||||||
|
DisableTUF: true,
|
||||||
}
|
}
|
||||||
src, err := oci.ParseImageSpec(referencedImage)
|
src, err := oci.ParseImageSpec(referencedImage)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|||||||
@@ -167,15 +167,16 @@ func resolvePolicyByID(opts *Options, tufClient tuf.Downloader) (*Policy, error)
|
|||||||
return resolveLocalPolicy(opts, policy, "", "")
|
return resolveLocalPolicy(opts, policy, "", "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !opts.DisableTUF {
|
||||||
// must check tuf
|
// must check tuf
|
||||||
tufMappings, err := config.LoadTUFMappings(tufClient, opts.LocalTargetsDir)
|
tufMappings, err := config.LoadTUFMappings(tufClient, opts.LocalTargetsDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to load tuf policy mappings by id: %w", err)
|
return nil, fmt.Errorf("failed to load tuf policy mappings by id: %w", err)
|
||||||
}
|
}
|
||||||
policy := tufMappings.Policies[opts.PolicyID]
|
policy := tufMappings.Policies[opts.PolicyID]
|
||||||
if policy != nil {
|
if policy != nil {
|
||||||
return resolveTUFPolicy(opts, tufClient, policy, "", "")
|
return resolveTUFPolicy(opts, tufClient, policy, "", "")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("policy with id %s not found", opts.PolicyID)
|
return nil, fmt.Errorf("policy with id %s not found", opts.PolicyID)
|
||||||
}
|
}
|
||||||
@@ -209,29 +210,31 @@ func ResolvePolicy(ctx context.Context, tufClient tuf.Downloader, detailsResolve
|
|||||||
if match.matchType == matchTypePolicy {
|
if match.matchType == matchTypePolicy {
|
||||||
return resolveLocalPolicy(opts, match.policy, imageName, match.matchedName)
|
return resolveLocalPolicy(opts, match.policy, imageName, match.matchedName)
|
||||||
}
|
}
|
||||||
// must check tuf
|
if !opts.DisableTUF {
|
||||||
tufMappings, err := config.LoadTUFMappings(tufClient, opts.LocalTargetsDir)
|
// must check tuf
|
||||||
if err != nil {
|
tufMappings, err := config.LoadTUFMappings(tufClient, opts.LocalTargetsDir)
|
||||||
return nil, fmt.Errorf("failed to load tuf policy mappings as fallback: %w", err)
|
if err != nil {
|
||||||
}
|
return nil, fmt.Errorf("failed to load tuf policy mappings as fallback: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// it's a mirror of a tuf policy
|
// it's a mirror of a tuf policy
|
||||||
if match.matchType == matchTypeMatchNoPolicy {
|
if match.matchType == matchTypeMatchNoPolicy {
|
||||||
for _, mapping := range tufMappings.Policies {
|
for _, mapping := range tufMappings.Policies {
|
||||||
if mapping.ID == match.rule.PolicyID {
|
if mapping.ID == match.rule.PolicyID {
|
||||||
return resolveTUFPolicy(opts, tufClient, mapping, imageName, match.matchedName)
|
return resolveTUFPolicy(opts, tufClient, mapping, imageName, match.matchedName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// try to resolve a tuf policy directly
|
||||||
|
match, err = findPolicyMatch(imageName, tufMappings)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if match.matchType == matchTypePolicy {
|
||||||
|
return resolveTUFPolicy(opts, tufClient, match.policy, imageName, match.matchedName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to resolve a tuf policy directly
|
|
||||||
match, err = findPolicyMatch(imageName, tufMappings)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if match.matchType == matchTypePolicy {
|
|
||||||
return resolveTUFPolicy(opts, tufClient, match.policy, imageName, match.matchedName)
|
|
||||||
}
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
"github.com/docker/attest/pkg/config"
|
"github.com/docker/attest/pkg/config"
|
||||||
"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/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -51,15 +50,15 @@ func TestRegoEvaluator_Evaluate(t *testing.T) {
|
|||||||
policyID string
|
policyID string
|
||||||
resolveErrorStr string
|
resolveErrorStr string
|
||||||
}{
|
}{
|
||||||
{repo: "testdata/mock-tuf-allow", expectSuccess: true, resolver: defaultResolver},
|
{repo: "testdata/policies/allow", expectSuccess: true, resolver: defaultResolver},
|
||||||
{repo: "testdata/mock-tuf-allow", expectSuccess: true, resolver: defaultResolver, policyID: "docker-official-images"},
|
{repo: "testdata/policies/allow", expectSuccess: true, resolver: defaultResolver, policyID: "docker-official-images"},
|
||||||
{repo: "testdata/mock-tuf-allow", resolver: defaultResolver, policyID: "non-existent-policy-id", resolveErrorStr: resolveErrorStr},
|
{repo: "testdata/policies/allow", resolver: defaultResolver, policyID: "non-existent-policy-id", resolveErrorStr: resolveErrorStr},
|
||||||
{repo: "testdata/mock-tuf-deny", resolver: defaultResolver},
|
{repo: "testdata/policies/deny", resolver: defaultResolver},
|
||||||
{repo: "testdata/mock-tuf-verify-sig", expectSuccess: true, resolver: defaultResolver},
|
{repo: "testdata/policies/verify-sig", expectSuccess: true, resolver: defaultResolver},
|
||||||
{repo: "testdata/mock-tuf-wrong-key", resolver: defaultResolver},
|
{repo: "testdata/policies/wrong-key", resolver: defaultResolver},
|
||||||
{repo: "testdata/mock-tuf-allow-canonical", expectSuccess: true, isCanonical: true, resolver: defaultResolver},
|
{repo: "testdata/policies/allow-canonical", expectSuccess: true, isCanonical: true, resolver: defaultResolver},
|
||||||
{repo: "testdata/mock-tuf-allow-canonical", resolver: defaultResolver},
|
{repo: "testdata/policies/allow-canonical", resolver: defaultResolver},
|
||||||
{repo: "testdata/mock-tuf-no-rego", resolver: defaultResolver, resolveErrorStr: "no policy file found in policy mapping"},
|
{repo: "testdata/policies/no-rego", resolver: defaultResolver, resolveErrorStr: "no policy file found in policy mapping"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
@@ -72,11 +71,12 @@ func TestRegoEvaluator_Evaluate(t *testing.T) {
|
|||||||
input.Tag = "test"
|
input.Tag = "test"
|
||||||
}
|
}
|
||||||
|
|
||||||
tufClient := tuf.NewMockTufClient(tc.repo, test.CreateTempDir(t, "", "tuf-dest"))
|
|
||||||
if tc.policy == nil {
|
if tc.policy == nil {
|
||||||
tc.policy = &policy.Options{
|
tc.policy = &policy.Options{
|
||||||
LocalTargetsDir: test.CreateTempDir(t, "", "tuf-targets"),
|
LocalTargetsDir: test.CreateTempDir(t, "", "tuf-targets"),
|
||||||
PolicyID: tc.policyID,
|
PolicyID: tc.policyID,
|
||||||
|
LocalPolicyDir: tc.repo,
|
||||||
|
DisableTUF: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
imageName, err := tc.resolver.ImageName(ctx)
|
imageName, err := tc.resolver.ImageName(ctx)
|
||||||
@@ -87,7 +87,7 @@ func TestRegoEvaluator_Evaluate(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
resolver, err := policy.CreateImageDetailsResolver(src)
|
resolver, err := policy.CreateImageDetailsResolver(src)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
policy, err := policy.ResolvePolicy(ctx, tufClient, resolver, tc.policy)
|
policy, err := policy.ResolvePolicy(ctx, nil, resolver, tc.policy)
|
||||||
if tc.resolveErrorStr != "" {
|
if tc.resolveErrorStr != "" {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.Contains(t, err.Error(), tc.resolveErrorStr)
|
assert.Contains(t, err.Error(), tc.resolveErrorStr)
|
||||||
@@ -108,7 +108,7 @@ func TestRegoEvaluator_Evaluate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadingMappings(t *testing.T) {
|
func TestLoadingMappings(t *testing.T) {
|
||||||
policyMappings, err := config.LoadLocalMappings(filepath.Join("testdata", "mock-tuf-allow"))
|
policyMappings, err := config.LoadLocalMappings(filepath.Join("testdata", "policies", "allow"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, len(policyMappings.Rules), 3)
|
assert.Equal(t, len(policyMappings.Rules), 3)
|
||||||
for _, mirror := range policyMappings.Rules {
|
for _, mirror := range policyMappings.Rules {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ type Result struct {
|
|||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
TUFClientOptions *tuf.ClientOptions
|
TUFClientOptions *tuf.ClientOptions
|
||||||
|
DisableTUF bool
|
||||||
LocalTargetsDir string
|
LocalTargetsDir string
|
||||||
LocalPolicyDir string
|
LocalPolicyDir string
|
||||||
PolicyID string
|
PolicyID string
|
||||||
|
|||||||
@@ -1,67 +1,5 @@
|
|||||||
package tuf
|
package tuf
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/docker/attest/internal/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
type MockTufClient struct {
|
|
||||||
srcPath string
|
|
||||||
dstPath string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMockTufClient(srcPath string, dstPath string) *MockTufClient {
|
|
||||||
if srcPath == "" {
|
|
||||||
panic("srcPath must be set")
|
|
||||||
}
|
|
||||||
if dstPath == "" {
|
|
||||||
panic("dstPath must be set")
|
|
||||||
}
|
|
||||||
return &MockTufClient{
|
|
||||||
srcPath: srcPath,
|
|
||||||
dstPath: dstPath,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *MockTufClient) DownloadTarget(target string, filePath string) (file *TargetFile, err error) {
|
|
||||||
targetPath := filepath.Join(dc.srcPath, target)
|
|
||||||
src, err := os.Open(targetPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer src.Close()
|
|
||||||
|
|
||||||
var dstFilePath string
|
|
||||||
if filePath == "" {
|
|
||||||
dstFilePath = filepath.Join(dc.dstPath, filepath.FromSlash(target))
|
|
||||||
} else {
|
|
||||||
dstFilePath = filePath
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.MkdirAll(filepath.Dir(dstFilePath), os.ModePerm)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
dst, err := os.Create(dstFilePath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer dst.Close()
|
|
||||||
|
|
||||||
// reading from tee will read from src and write to dst at the same time
|
|
||||||
tee := io.TeeReader(src, dst)
|
|
||||||
|
|
||||||
b, err := io.ReadAll(tee)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &TargetFile{ActualFilePath: dstFilePath, TargetURI: targetPath, Data: b, Digest: util.SHA256Hex(b)}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type MockVersionChecker struct {
|
type MockVersionChecker struct {
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package tuf
|
package tuf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
@@ -21,24 +20,6 @@ import (
|
|||||||
"github.com/theupdateframework/go-tuf/v2/metadata/updater"
|
"github.com/theupdateframework/go-tuf/v2/metadata/updater"
|
||||||
)
|
)
|
||||||
|
|
||||||
type tufCtxKeyType struct{}
|
|
||||||
|
|
||||||
var DownloaderCtxKey tufCtxKeyType
|
|
||||||
|
|
||||||
// WithDownloader sets Downloader in context.
|
|
||||||
func WithDownloader(ctx context.Context, downloader Downloader) context.Context {
|
|
||||||
return context.WithValue(ctx, DownloaderCtxKey, downloader)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDownloader returns the Downloader from context and `true` if it exists, otherwise `nil` and `false`.
|
|
||||||
func GetDownloader(ctx context.Context) (Downloader, bool) {
|
|
||||||
t, ok := ctx.Value(DownloaderCtxKey).(Downloader)
|
|
||||||
if !ok {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
return t, true
|
|
||||||
}
|
|
||||||
|
|
||||||
type Source string
|
type Source string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
Reference in New Issue
Block a user