From f055517baa67f09c5a5668d7e0dfc4892f538d79 Mon Sep 17 00:00:00 2001 From: Meredith Lancaster Date: Tue, 12 Mar 2024 16:12:45 -0600 Subject: [PATCH] create interface for oci client Signed-off-by: Meredith Lancaster --- pkg/cmd/attestation/artifact/artifact.go | 2 +- pkg/cmd/attestation/artifact/image.go | 2 +- pkg/cmd/attestation/artifact/image_test.go | 10 +- .../attestation/artifact/oci/mock_client.go | 35 ++++++ pkg/cmd/attestation/artifact/oci/oci.go | 106 +++++++++--------- pkg/cmd/attestation/download/download_test.go | 12 +- pkg/cmd/attestation/download/options.go | 2 +- pkg/cmd/attestation/inspect/inspect_test.go | 2 +- pkg/cmd/attestation/inspect/options.go | 2 +- pkg/cmd/attestation/verify/options.go | 2 +- pkg/cmd/attestation/verify/policy_test.go | 2 +- pkg/cmd/attestation/verify/verify_test.go | 4 +- 12 files changed, 110 insertions(+), 71 deletions(-) create mode 100644 pkg/cmd/attestation/artifact/oci/mock_client.go diff --git a/pkg/cmd/attestation/artifact/artifact.go b/pkg/cmd/attestation/artifact/artifact.go index 04dc1642f..dd39eacd5 100644 --- a/pkg/cmd/attestation/artifact/artifact.go +++ b/pkg/cmd/attestation/artifact/artifact.go @@ -51,7 +51,7 @@ func normalizeReference(reference string, pathSeparator rune) (normalized string return filepath.Clean(reference), fileArtifactType, nil } -func NewDigestedArtifact(client *oci.Client, reference, digestAlg string) (artifact *DigestedArtifact, err error) { +func NewDigestedArtifact(client oci.Client, reference, digestAlg string) (artifact *DigestedArtifact, err error) { normalized, artifactType, err := normalizeReference(reference, os.PathSeparator) if err != nil { return nil, err diff --git a/pkg/cmd/attestation/artifact/image.go b/pkg/cmd/attestation/artifact/image.go index 32c4f77ad..d26922d33 100644 --- a/pkg/cmd/attestation/artifact/image.go +++ b/pkg/cmd/attestation/artifact/image.go @@ -7,7 +7,7 @@ import ( "github.com/distribution/reference" ) -func digestContainerImageArtifact(url string, client *oci.Client) (*DigestedArtifact, error) { +func digestContainerImageArtifact(url string, client oci.Client) (*DigestedArtifact, error) { if client == nil { return nil, fmt.Errorf("missing OCI client") } diff --git a/pkg/cmd/attestation/artifact/image_test.go b/pkg/cmd/attestation/artifact/image_test.go index 55d7388d2..5ea5f9a37 100644 --- a/pkg/cmd/attestation/artifact/image_test.go +++ b/pkg/cmd/attestation/artifact/image_test.go @@ -10,7 +10,7 @@ import ( func TestDigestContainerImageArtifact(t *testing.T) { expectedDigest := "1234567890abcdef" - client := oci.NewMockClient() + client := oci.MockClient{} url := "example.com/repo:tag" digestedArtifact, err := digestContainerImageArtifact(url, client) require.NoError(t, err) @@ -20,7 +20,7 @@ func TestDigestContainerImageArtifact(t *testing.T) { } func TestParseImageRefFailure(t *testing.T) { - client := oci.NewReferenceFailClient() + client := oci.ReferenceFailClient{} url := "example.com/repo:tag" _, err := digestContainerImageArtifact(url, client) require.Error(t, err) @@ -29,17 +29,17 @@ func TestParseImageRefFailure(t *testing.T) { func TestFetchImageFailure(t *testing.T) { testcase := []struct { name string - client *oci.Client + client oci.Client expectedErr error }{ { name: "Fail to authorize with registry", - client: oci.NewAuthFailClient(), + client: oci.AuthFailClient{}, expectedErr: oci.ErrRegistryAuthz, }, { name: "Fail to fetch image due to denial", - client: oci.NewDeniedClient(), + client: oci.DeniedClient{}, expectedErr: oci.ErrDenied, }, } diff --git a/pkg/cmd/attestation/artifact/oci/mock_client.go b/pkg/cmd/attestation/artifact/oci/mock_client.go new file mode 100644 index 000000000..dface9b00 --- /dev/null +++ b/pkg/cmd/attestation/artifact/oci/mock_client.go @@ -0,0 +1,35 @@ +package oci + +import ( + "fmt" + + "github.com/google/go-containerregistry/pkg/v1" +) + + +type MockClient struct {} + +func (c MockClient) GetImageDigest(imgName string) (*v1.Hash, error) { + return &v1.Hash{ + Hex: "1234567890abcdef", + Algorithm: "sha256", + }, nil +} + +type ReferenceFailClient struct {} + +func (c ReferenceFailClient) GetImageDigest(imgName string) (*v1.Hash, error) { + return nil, fmt.Errorf("failed to parse reference") +} + +type AuthFailClient struct {} + +func (c AuthFailClient) GetImageDigest(imgName string) (*v1.Hash, error) { + return nil, ErrRegistryAuthz +} + +type DeniedClient struct {} + +func (c DeniedClient) GetImageDigest(imgName string) (*v1.Hash, error) { + return nil, ErrDenied +} diff --git a/pkg/cmd/attestation/artifact/oci/oci.go b/pkg/cmd/attestation/artifact/oci/oci.go index 4d7a02586..7028b1c0c 100644 --- a/pkg/cmd/attestation/artifact/oci/oci.go +++ b/pkg/cmd/attestation/artifact/oci/oci.go @@ -14,9 +14,8 @@ import ( var ErrDenied = errors.New("the provided token was denied access to the requested resource, please check the token's expiration and repository access") var ErrRegistryAuthz = errors.New("remote registry authorization failed, please authenticate with the registry and try again") -type Client struct { - ParseReference func(string, ...name.Option) (name.Reference, error) - Get func(name.Reference, ...remote.Option) (*remote.Descriptor, error) +type Client interface { + GetImageDigest(imgName string) (*v1.Hash, error) } func checkForUnauthorizedOrDeniedErr(err transport.Error) error { @@ -31,8 +30,13 @@ func checkForUnauthorizedOrDeniedErr(err transport.Error) error { return nil } +type LiveClient struct { + ParseReference func(string, ...name.Option) (name.Reference, error) + Get func(name.Reference, ...remote.Option) (*remote.Descriptor, error) +} + // where name is formed like ghcr.io/github/my-image-repo -func (c Client) GetImageDigest(imgName string) (*v1.Hash, error) { +func (c LiveClient) GetImageDigest(imgName string) (*v1.Hash, error) { name, err := c.ParseReference(imgName) if err != nil { return nil, fmt.Errorf("failed to create image tag: %w", err) @@ -52,59 +56,59 @@ func (c Client) GetImageDigest(imgName string) (*v1.Hash, error) { return &desc.Digest, nil } -func NewLiveClient() *Client { - return &Client{ +func NewLiveClient() *LiveClient { + return &LiveClient{ ParseReference: name.ParseReference, Get: remote.Get, } } -func NewMockClient() *Client { - return &Client{ - ParseReference: func(string, ...name.Option) (name.Reference, error) { - return name.Tag{}, nil - }, - Get: func(name.Reference, ...remote.Option) (*remote.Descriptor, error) { - d := remote.Descriptor{} - d.Digest = v1.Hash{ - Hex: "1234567890abcdef", - Algorithm: "sha256", - } +// func NewMockClient() *Client { +// return &Client{ +// ParseReference: func(string, ...name.Option) (name.Reference, error) { +// return name.Tag{}, nil +// }, +// Get: func(name.Reference, ...remote.Option) (*remote.Descriptor, error) { +// d := remote.Descriptor{} +// d.Digest = v1.Hash{ +// Hex: "1234567890abcdef", +// Algorithm: "sha256", +// } - return &d, nil - }, - } -} +// return &d, nil +// }, +// } +// } -func NewReferenceFailClient() *Client { - return &Client{ - ParseReference: func(string, ...name.Option) (name.Reference, error) { - return nil, fmt.Errorf("failed to parse reference") - }, - Get: func(name.Reference, ...remote.Option) (*remote.Descriptor, error) { - return nil, nil - }, - } -} +// func NewReferenceFailClient() *Client { +// return &Client{ +// ParseReference: func(string, ...name.Option) (name.Reference, error) { +// return nil, fmt.Errorf("failed to parse reference") +// }, +// Get: func(name.Reference, ...remote.Option) (*remote.Descriptor, error) { +// return nil, nil +// }, +// } +// } -func NewAuthFailClient() *Client { - return &Client{ - ParseReference: func(string, ...name.Option) (name.Reference, error) { - return name.Tag{}, nil - }, - Get: func(name.Reference, ...remote.Option) (*remote.Descriptor, error) { - return nil, &transport.Error{Errors: []transport.Diagnostic{{Code: transport.UnauthorizedErrorCode}}} - }, - } -} +// func NewAuthFailClient() *Client { +// return &Client{ +// ParseReference: func(string, ...name.Option) (name.Reference, error) { +// return name.Tag{}, nil +// }, +// Get: func(name.Reference, ...remote.Option) (*remote.Descriptor, error) { +// return nil, &transport.Error{Errors: []transport.Diagnostic{{Code: transport.UnauthorizedErrorCode}}} +// }, +// } +// } -func NewDeniedClient() *Client { - return &Client{ - ParseReference: func(string, ...name.Option) (name.Reference, error) { - return name.Tag{}, nil - }, - Get: func(name.Reference, ...remote.Option) (*remote.Descriptor, error) { - return nil, &transport.Error{Errors: []transport.Diagnostic{{Code: transport.DeniedErrorCode}}} - }, - } -} +// func NewDeniedClient() *Client { +// return &Client{ +// ParseReference: func(string, ...name.Option) (name.Reference, error) { +// return name.Tag{}, nil +// }, +// Get: func(name.Reference, ...remote.Option) (*remote.Descriptor, error) { +// return nil, &transport.Error{Errors: []transport.Diagnostic{{Code: transport.DeniedErrorCode}}} +// }, +// } +// } diff --git a/pkg/cmd/attestation/download/download_test.go b/pkg/cmd/attestation/download/download_test.go index 1b1adbcb6..79fe1f001 100644 --- a/pkg/cmd/attestation/download/download_test.go +++ b/pkg/cmd/attestation/download/download_test.go @@ -22,7 +22,7 @@ func TestRunDownload(t *testing.T) { baseOpts := Options{ ArtifactPath: "../test/data/sigstore-js-2.1.0.tgz", APIClient: api.NewTestClient(), - OCIClient: oci.NewMockClient(), + OCIClient: oci.MockClient{}, DigestAlgorithm: "sha512", OutputPath: tempDir, Limit: 30, @@ -97,7 +97,7 @@ func TestRunDownload(t *testing.T) { t.Run("cannot download OCI artifact", func(t *testing.T) { opts := baseOpts opts.ArtifactPath = "oci://ghcr.io/github/test" - opts.OCIClient = oci.NewReferenceFailClient() + opts.OCIClient = oci.ReferenceFailClient{} err := RunDownload(&opts) require.Error(t, err) @@ -124,7 +124,7 @@ func TestCreateJSONLinesFilePath(t *testing.T) { defer os.RemoveAll(tempDir) t.Run("with output path", func(t *testing.T) { - artifact, err := artifact.NewDigestedArtifact(oci.NewMockClient(), "../test/data/sigstore-js-2.1.0.tgz", "sha512") + artifact, err := artifact.NewDigestedArtifact(oci.MockClient{}, "../test/data/sigstore-js-2.1.0.tgz", "sha512") require.NoError(t, err) path := createJSONLinesFilePath(artifact.DigestWithAlg(), tempDir) @@ -133,7 +133,7 @@ func TestCreateJSONLinesFilePath(t *testing.T) { }) t.Run("with nested output path", func(t *testing.T) { - artifact, err := artifact.NewDigestedArtifact(oci.NewMockClient(), "../test/data/sigstore-js-2.1.0.tgz", "sha512") + artifact, err := artifact.NewDigestedArtifact(oci.MockClient{}, "../test/data/sigstore-js-2.1.0.tgz", "sha512") require.NoError(t, err) nestedPath := fmt.Sprintf("%s/subdir", tempDir) @@ -144,7 +144,7 @@ func TestCreateJSONLinesFilePath(t *testing.T) { }) t.Run("with output path with beginning slash", func(t *testing.T) { - artifact, err := artifact.NewDigestedArtifact(oci.NewMockClient(), "../test/data/sigstore-js-2.1.0.tgz", "sha512") + artifact, err := artifact.NewDigestedArtifact(oci.MockClient{}, "../test/data/sigstore-js-2.1.0.tgz", "sha512") require.NoError(t, err) nestedPath := fmt.Sprintf("/%s/subdir", tempDir) @@ -155,7 +155,7 @@ func TestCreateJSONLinesFilePath(t *testing.T) { }) t.Run("without output path", func(t *testing.T) { - artifact, err := artifact.NewDigestedArtifact(oci.NewMockClient(), "../test/data/sigstore-js-2.1.0.tgz", "sha512") + artifact, err := artifact.NewDigestedArtifact(oci.MockClient{}, "../test/data/sigstore-js-2.1.0.tgz", "sha512") require.NoError(t, err) path := createJSONLinesFilePath(artifact.DigestWithAlg(), "") diff --git a/pkg/cmd/attestation/download/options.go b/pkg/cmd/attestation/download/options.go index 68f858534..354140820 100644 --- a/pkg/cmd/attestation/download/options.go +++ b/pkg/cmd/attestation/download/options.go @@ -15,7 +15,7 @@ type Options struct { DigestAlgorithm string Logger *logging.Logger Limit int - OCIClient *oci.Client + OCIClient oci.Client OutputPath string Owner string Verbose bool diff --git a/pkg/cmd/attestation/inspect/inspect_test.go b/pkg/cmd/attestation/inspect/inspect_test.go index 34249452e..d11f91005 100644 --- a/pkg/cmd/attestation/inspect/inspect_test.go +++ b/pkg/cmd/attestation/inspect/inspect_test.go @@ -26,7 +26,7 @@ func TestRunInspect(t *testing.T) { BundlePath: bundlePath, DigestAlgorithm: "sha512", Logger: logging.NewTestLogger(), - OCIClient: oci.NewMockClient(), + OCIClient: oci.MockClient{}, } t.Run("with valid artifact and bundle", func(t *testing.T) { diff --git a/pkg/cmd/attestation/inspect/options.go b/pkg/cmd/attestation/inspect/options.go index a8acbc77a..eaedd73f1 100644 --- a/pkg/cmd/attestation/inspect/options.go +++ b/pkg/cmd/attestation/inspect/options.go @@ -18,7 +18,7 @@ type Options struct { Verbose bool Quiet bool Logger *logging.Logger - OCIClient *oci.Client + OCIClient oci.Client } // Clean cleans the file path option values diff --git a/pkg/cmd/attestation/verify/options.go b/pkg/cmd/attestation/verify/options.go index 059891428..19b1f8008 100644 --- a/pkg/cmd/attestation/verify/options.go +++ b/pkg/cmd/attestation/verify/options.go @@ -30,7 +30,7 @@ type Options struct { APIClient api.Client Logger *logging.Logger Limit int - OCIClient *oci.Client + OCIClient oci.Client } // Clean cleans the file path option values diff --git a/pkg/cmd/attestation/verify/policy_test.go b/pkg/cmd/attestation/verify/policy_test.go index 085cc3238..76a299488 100644 --- a/pkg/cmd/attestation/verify/policy_test.go +++ b/pkg/cmd/attestation/verify/policy_test.go @@ -10,7 +10,7 @@ import ( ) func TestBuildPolicy(t *testing.T) { - ociClient := oci.NewMockClient() + ociClient := oci.MockClient{} artifactPath := "../test/data/sigstore-js-2.1.0.tgz" digestAlg := "sha256" diff --git a/pkg/cmd/attestation/verify/verify_test.go b/pkg/cmd/attestation/verify/verify_test.go index 4f1c5a5f9..0baf7cd67 100644 --- a/pkg/cmd/attestation/verify/verify_test.go +++ b/pkg/cmd/attestation/verify/verify_test.go @@ -29,7 +29,7 @@ func TestRunVerify(t *testing.T) { DigestAlgorithm: "sha512", APIClient: api.NewTestClient(), Logger: logger, - OCIClient: oci.NewMockClient(), + OCIClient: oci.MockClient{}, OIDCIssuer: GitHubOIDCIssuer, Owner: "sigstore", SANRegex: "^https://github.com/sigstore/", @@ -42,7 +42,7 @@ func TestRunVerify(t *testing.T) { t.Run("with failing OCI artifact fetch", func(t *testing.T) { opts := publicGoodOpts opts.ArtifactPath = "oci://ghcr.io/github/test" - opts.OCIClient = oci.NewReferenceFailClient() + opts.OCIClient = oci.ReferenceFailClient{} err := RunVerify(&opts) require.Error(t, err)