From c6015b57074c712a4df01620654e6f8dd678dd40 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Tue, 8 Nov 2022 13:38:36 +0800 Subject: [PATCH 01/10] registry refactoring Signed-off-by: Patrick Zheng --- internal/mock/mocks.go | 2 +- {registry => internal/registry}/interface.go | 0 internal/registry/mediatype.go | 4 + internal/registry/repository.go | 198 +++++++ .../registry}/repository_test.go | 0 registry/repository.go | 208 +------- registry/repositoryClient.go | 163 ++++++ registry/repositoryClient_test.go | 499 ++++++++++++++++++ verification/verifier.go | 2 +- verification/verifier_helpers.go | 2 +- verification/verifier_test.go | 2 +- 11 files changed, 889 insertions(+), 191 deletions(-) rename {registry => internal/registry}/interface.go (100%) create mode 100644 internal/registry/mediatype.go create mode 100644 internal/registry/repository.go rename {registry => internal/registry}/repository_test.go (100%) create mode 100644 registry/repositoryClient.go create mode 100644 registry/repositoryClient_test.go diff --git a/internal/mock/mocks.go b/internal/mock/mocks.go index 7903b9f1..13a40dd1 100644 --- a/internal/mock/mocks.go +++ b/internal/mock/mocks.go @@ -6,9 +6,9 @@ import ( "github.com/notaryproject/notation-core-go/signature" "github.com/notaryproject/notation-go" + "github.com/notaryproject/notation-go/internal/registry" "github.com/notaryproject/notation-go/plugin" "github.com/notaryproject/notation-go/plugin/manager" - "github.com/notaryproject/notation-go/registry" "github.com/opencontainers/go-digest" ) diff --git a/registry/interface.go b/internal/registry/interface.go similarity index 100% rename from registry/interface.go rename to internal/registry/interface.go diff --git a/internal/registry/mediatype.go b/internal/registry/mediatype.go new file mode 100644 index 00000000..a7350d19 --- /dev/null +++ b/internal/registry/mediatype.go @@ -0,0 +1,4 @@ +package registry + +// ArtifactTypeNotation specifies the artifact type for a notation object. +const ArtifactTypeNotation = "application/vnd.cncf.notary.v2.signature" diff --git a/internal/registry/repository.go b/internal/registry/repository.go new file mode 100644 index 00000000..37f38e14 --- /dev/null +++ b/internal/registry/repository.go @@ -0,0 +1,198 @@ +package registry + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + + "github.com/notaryproject/notation-go" + "github.com/opencontainers/go-digest" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + artifactspec "github.com/oras-project/artifacts-spec/specs-go/v1" + "oras.land/oras-go/v2" + "oras.land/oras-go/v2/content" + "oras.land/oras-go/v2/registry" + "oras.land/oras-go/v2/registry/remote" +) + +const ( + maxBlobSizeLimit = 32 * 1024 * 1024 // 32 MiB + maxManifestSizeLimit = 4 * 1024 * 1024 // 4 MiB +) + +type RepositoryClient struct { + remote.Repository +} + +type SignatureManifest struct { + Blob notation.Descriptor + Annotations map[string]string +} + +// NewRepositoryClient creates a new registry client. +func NewRepositoryClient(client remote.Client, ref registry.Reference, plainHTTP bool) *RepositoryClient { + return &RepositoryClient{ + Repository: remote.Repository{ + Client: client, + Reference: ref, + PlainHTTP: plainHTTP, + }, + } +} + +// Resolve resolves a reference(tag or digest) to a manifest descriptor +func (c *RepositoryClient) Resolve(ctx context.Context, reference string) (notation.Descriptor, error) { + desc, err := c.Repository.Resolve(ctx, reference) + if err != nil { + return notation.Descriptor{}, err + } + return notationDescriptorFromOCI(desc), nil +} + +// ListSignatureManifests returns all signature manifests given the manifest digest +func (c *RepositoryClient) ListSignatureManifests(ctx context.Context, manifestDigest digest.Digest) ([]SignatureManifest, error) { + var signatureManifests []SignatureManifest + if err := c.Repository.Referrers(ctx, ocispec.Descriptor{ + Digest: manifestDigest, + }, ArtifactTypeNotation, func(referrers []artifactspec.Descriptor) error { + for _, desc := range referrers { + if desc.MediaType != artifactspec.MediaTypeArtifactManifest { + continue + } + artifact, err := c.getArtifactManifest(ctx, desc.Digest) + if err != nil { + return fmt.Errorf("failed to fetch manifest: %v: %v", desc.Digest, err) + } + if len(artifact.Blobs) == 0 { + continue + } + signatureManifests = append(signatureManifests, SignatureManifest{ + Blob: notationDescriptorFromArtifact(artifact.Blobs[0]), + Annotations: artifact.Annotations, + }) + } + return nil + }); err != nil { + return nil, err + } + return signatureManifests, nil +} + +// GetBlob downloads the content of the specified digest's Blob +func (c *RepositoryClient) GetBlob(ctx context.Context, digest digest.Digest) ([]byte, error) { + desc, err := c.Repository.Blobs().Resolve(ctx, digest.String()) + if err != nil { + return nil, err + } + if desc.Size > maxBlobSizeLimit { + return nil, fmt.Errorf("signature blob too large: %d", desc.Size) + } + return content.FetchAll(ctx, c.Repository.Blobs(), desc) +} + +// PutSignatureManifest creates and uploads an signature artifact linking the manifest and the signature +func (c *RepositoryClient) PutSignatureManifest(ctx context.Context, signature []byte, signatureMediaType string, subjectManifest notation.Descriptor, annotations map[string]string) (notation.Descriptor, SignatureManifest, error) { + signatureDesc, err := c.uploadSignature(ctx, signature, signatureMediaType) + if err != nil { + return notation.Descriptor{}, SignatureManifest{}, err + } + + manifestDesc, err := c.uploadSignatureManifest(ctx, artifactDescriptorFromNotation(subjectManifest), signatureDesc, annotations) + if err != nil { + return notation.Descriptor{}, SignatureManifest{}, err + } + + signatureManifest := SignatureManifest{ + Blob: notationDescriptorFromArtifact(signatureDesc), + Annotations: annotations, + } + return notationDescriptorFromOCI(manifestDesc), signatureManifest, nil +} + +func (c *RepositoryClient) getArtifactManifest(ctx context.Context, manifestDigest digest.Digest) (artifactspec.Manifest, error) { + repo := c.Repository + repo.ManifestMediaTypes = []string{ + artifactspec.MediaTypeArtifactManifest, + } + store := repo.Manifests() + desc, err := store.Resolve(ctx, manifestDigest.String()) + if err != nil { + return artifactspec.Manifest{}, err + } + if desc.Size > maxManifestSizeLimit { + return artifactspec.Manifest{}, fmt.Errorf("manifest too large: %d", desc.Size) + } + manifestJSON, err := content.FetchAll(ctx, store, desc) + if err != nil { + return artifactspec.Manifest{}, err + } + + var manifest artifactspec.Manifest + err = json.Unmarshal(manifestJSON, &manifest) + if err != nil { + return artifactspec.Manifest{}, err + } + return manifest, nil +} + +// uploadSignature uploads the signature to the registry +func (c *RepositoryClient) uploadSignature(ctx context.Context, signature []byte, signatureMediaType string) (artifactspec.Descriptor, error) { + desc := ocispec.Descriptor{ + MediaType: signatureMediaType, + Digest: digest.FromBytes(signature), + Size: int64(len(signature)), + } + if err := c.Repository.Blobs().Push(ctx, desc, bytes.NewReader(signature)); err != nil { + return artifactspec.Descriptor{}, err + } + return artifactDescriptorFromOCI(desc), nil +} + +// uploadSignatureManifest uploads the signature manifest to the registry +func (c *RepositoryClient) uploadSignatureManifest(ctx context.Context, subjectManifest, signatureDesc artifactspec.Descriptor, annotations map[string]string) (ocispec.Descriptor, error) { + opts := oras.PackArtifactOptions{ + Subject: &subjectManifest, + ManifestAnnotations: annotations, + } + + return oras.PackArtifact( + ctx, + c.Repository.Manifests(), + ArtifactTypeNotation, + []artifactspec.Descriptor{signatureDesc}, + opts, + ) +} + +func artifactDescriptorFromNotation(desc notation.Descriptor) artifactspec.Descriptor { + return artifactspec.Descriptor{ + MediaType: desc.MediaType, + Digest: desc.Digest, + Size: desc.Size, + } +} + +func notationDescriptorFromArtifact(desc artifactspec.Descriptor) notation.Descriptor { + return notation.Descriptor{ + MediaType: desc.MediaType, + Digest: desc.Digest, + Size: desc.Size, + } +} + +func artifactDescriptorFromOCI(desc ocispec.Descriptor) artifactspec.Descriptor { + return artifactspec.Descriptor{ + MediaType: desc.MediaType, + Digest: desc.Digest, + Size: desc.Size, + } +} + +func notationDescriptorFromOCI(desc ocispec.Descriptor) notation.Descriptor { + return notation.Descriptor{ + MediaType: desc.MediaType, + Digest: desc.Digest, + Size: desc.Size, + } +} diff --git a/registry/repository_test.go b/internal/registry/repository_test.go similarity index 100% rename from registry/repository_test.go rename to internal/registry/repository_test.go diff --git a/registry/repository.go b/registry/repository.go index 37f38e14..7406e2eb 100644 --- a/registry/repository.go +++ b/registry/repository.go @@ -1,198 +1,32 @@ +// Package registry provides Repository for remote signing and verification package registry import ( - "bytes" "context" - "encoding/json" - "fmt" - "github.com/notaryproject/notation-go" - "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" - artifactspec "github.com/oras-project/artifacts-spec/specs-go/v1" - "oras.land/oras-go/v2" - "oras.land/oras-go/v2/content" - "oras.land/oras-go/v2/registry" "oras.land/oras-go/v2/registry/remote" ) -const ( - maxBlobSizeLimit = 32 * 1024 * 1024 // 32 MiB - maxManifestSizeLimit = 4 * 1024 * 1024 // 4 MiB -) - -type RepositoryClient struct { - remote.Repository -} - -type SignatureManifest struct { - Blob notation.Descriptor - Annotations map[string]string -} - -// NewRepositoryClient creates a new registry client. -func NewRepositoryClient(client remote.Client, ref registry.Reference, plainHTTP bool) *RepositoryClient { - return &RepositoryClient{ - Repository: remote.Repository{ - Client: client, - Reference: ref, - PlainHTTP: plainHTTP, - }, - } -} - -// Resolve resolves a reference(tag or digest) to a manifest descriptor -func (c *RepositoryClient) Resolve(ctx context.Context, reference string) (notation.Descriptor, error) { - desc, err := c.Repository.Resolve(ctx, reference) - if err != nil { - return notation.Descriptor{}, err - } - return notationDescriptorFromOCI(desc), nil -} - -// ListSignatureManifests returns all signature manifests given the manifest digest -func (c *RepositoryClient) ListSignatureManifests(ctx context.Context, manifestDigest digest.Digest) ([]SignatureManifest, error) { - var signatureManifests []SignatureManifest - if err := c.Repository.Referrers(ctx, ocispec.Descriptor{ - Digest: manifestDigest, - }, ArtifactTypeNotation, func(referrers []artifactspec.Descriptor) error { - for _, desc := range referrers { - if desc.MediaType != artifactspec.MediaTypeArtifactManifest { - continue - } - artifact, err := c.getArtifactManifest(ctx, desc.Digest) - if err != nil { - return fmt.Errorf("failed to fetch manifest: %v: %v", desc.Digest, err) - } - if len(artifact.Blobs) == 0 { - continue - } - signatureManifests = append(signatureManifests, SignatureManifest{ - Blob: notationDescriptorFromArtifact(artifact.Blobs[0]), - Annotations: artifact.Annotations, - }) - } - return nil - }); err != nil { - return nil, err - } - return signatureManifests, nil -} - -// GetBlob downloads the content of the specified digest's Blob -func (c *RepositoryClient) GetBlob(ctx context.Context, digest digest.Digest) ([]byte, error) { - desc, err := c.Repository.Blobs().Resolve(ctx, digest.String()) - if err != nil { - return nil, err - } - if desc.Size > maxBlobSizeLimit { - return nil, fmt.Errorf("signature blob too large: %d", desc.Size) - } - return content.FetchAll(ctx, c.Repository.Blobs(), desc) -} - -// PutSignatureManifest creates and uploads an signature artifact linking the manifest and the signature -func (c *RepositoryClient) PutSignatureManifest(ctx context.Context, signature []byte, signatureMediaType string, subjectManifest notation.Descriptor, annotations map[string]string) (notation.Descriptor, SignatureManifest, error) { - signatureDesc, err := c.uploadSignature(ctx, signature, signatureMediaType) - if err != nil { - return notation.Descriptor{}, SignatureManifest{}, err - } - - manifestDesc, err := c.uploadSignatureManifest(ctx, artifactDescriptorFromNotation(subjectManifest), signatureDesc, annotations) - if err != nil { - return notation.Descriptor{}, SignatureManifest{}, err - } - - signatureManifest := SignatureManifest{ - Blob: notationDescriptorFromArtifact(signatureDesc), - Annotations: annotations, - } - return notationDescriptorFromOCI(manifestDesc), signatureManifest, nil -} - -func (c *RepositoryClient) getArtifactManifest(ctx context.Context, manifestDigest digest.Digest) (artifactspec.Manifest, error) { - repo := c.Repository - repo.ManifestMediaTypes = []string{ - artifactspec.MediaTypeArtifactManifest, - } - store := repo.Manifests() - desc, err := store.Resolve(ctx, manifestDigest.String()) - if err != nil { - return artifactspec.Manifest{}, err - } - if desc.Size > maxManifestSizeLimit { - return artifactspec.Manifest{}, fmt.Errorf("manifest too large: %d", desc.Size) - } - manifestJSON, err := content.FetchAll(ctx, store, desc) - if err != nil { - return artifactspec.Manifest{}, err - } - - var manifest artifactspec.Manifest - err = json.Unmarshal(manifestJSON, &manifest) - if err != nil { - return artifactspec.Manifest{}, err - } - return manifest, nil -} - -// uploadSignature uploads the signature to the registry -func (c *RepositoryClient) uploadSignature(ctx context.Context, signature []byte, signatureMediaType string) (artifactspec.Descriptor, error) { - desc := ocispec.Descriptor{ - MediaType: signatureMediaType, - Digest: digest.FromBytes(signature), - Size: int64(len(signature)), - } - if err := c.Repository.Blobs().Push(ctx, desc, bytes.NewReader(signature)); err != nil { - return artifactspec.Descriptor{}, err - } - return artifactDescriptorFromOCI(desc), nil -} - -// uploadSignatureManifest uploads the signature manifest to the registry -func (c *RepositoryClient) uploadSignatureManifest(ctx context.Context, subjectManifest, signatureDesc artifactspec.Descriptor, annotations map[string]string) (ocispec.Descriptor, error) { - opts := oras.PackArtifactOptions{ - Subject: &subjectManifest, - ManifestAnnotations: annotations, - } - - return oras.PackArtifact( - ctx, - c.Repository.Manifests(), - ArtifactTypeNotation, - []artifactspec.Descriptor{signatureDesc}, - opts, - ) -} - -func artifactDescriptorFromNotation(desc notation.Descriptor) artifactspec.Descriptor { - return artifactspec.Descriptor{ - MediaType: desc.MediaType, - Digest: desc.Digest, - Size: desc.Size, - } -} - -func notationDescriptorFromArtifact(desc artifactspec.Descriptor) notation.Descriptor { - return notation.Descriptor{ - MediaType: desc.MediaType, - Digest: desc.Digest, - Size: desc.Size, - } -} - -func artifactDescriptorFromOCI(desc ocispec.Descriptor) artifactspec.Descriptor { - return artifactspec.Descriptor{ - MediaType: desc.MediaType, - Digest: desc.Digest, - Size: desc.Size, - } -} - -func notationDescriptorFromOCI(desc ocispec.Descriptor) notation.Descriptor { - return notation.Descriptor{ - MediaType: desc.MediaType, - Digest: desc.Digest, - Size: desc.Size, +// Repository provides registry functionalities for remote signing and +// verification. +type Repository interface { + // Resolve resolves a reference(tag or digest) to a manifest descriptor + Resolve(ctx context.Context, reference string) (ocispec.Descriptor, error) + // ListSignatures returns signature manifests filtered by fn given the + // artifact manifest descriptor + ListSignatures(ctx context.Context, desc ocispec.Descriptor, fn func(signatureManifests []ocispec.Descriptor) error) error + // FetchSignatureBlob returns signature envelope blob and descriptor given + // signature manifest descriptor + FetchSignatureBlob(ctx context.Context, desc ocispec.Descriptor) ([]byte, ocispec.Descriptor, error) + // PushSignature creates and uploads an signature manifest along with its + // linked signature envelope blob. + PushSignature(ctx context.Context, blob []byte, mediaType string, subject ocispec.Descriptor, annotations map[string]string) (blobDesc, manifestDesc ocispec.Descriptor, err error) +} + +// NewRepository returns a new Repository +func NewRepository(repo remote.Repository) Repository { + return &repositoryClient{ + Repository: repo, } } diff --git a/registry/repositoryClient.go b/registry/repositoryClient.go new file mode 100644 index 00000000..806cd68d --- /dev/null +++ b/registry/repositoryClient.go @@ -0,0 +1,163 @@ +package registry + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + + "github.com/opencontainers/go-digest" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + artifactspec "github.com/oras-project/artifacts-spec/specs-go/v1" + "oras.land/oras-go/v2" + "oras.land/oras-go/v2/content" + "oras.land/oras-go/v2/registry/remote" +) + +const ( + maxBlobSizeLimit = 32 * 1024 * 1024 // 32 MiB + maxManifestSizeLimit = 4 * 1024 * 1024 // 4 MiB +) + +// repositoryClient implements Repository +type repositoryClient struct { + remote.Repository +} + +// Resolve resolves a reference(tag or digest) to a manifest descriptor +func (c *repositoryClient) Resolve(ctx context.Context, reference string) (ocispec.Descriptor, error) { + return c.Repository.Resolve(ctx, reference) +} + +// ListSignatures returns signature manifests filtered by fn given the +// artifact manifest descriptor +func (c *repositoryClient) ListSignatures(ctx context.Context, desc ocispec.Descriptor, fn func(signatureManifests []ocispec.Descriptor) error) error { + if err := c.Repository.Referrers(ctx, ocispec.Descriptor{ + Digest: desc.Digest, + }, ArtifactTypeNotation, func(referrers []artifactspec.Descriptor) error { + var sigManifestDesc []ocispec.Descriptor + for _, referrer := range referrers { + sigManifestDesc = append(sigManifestDesc, ocispecDescriptorFromArtifact(referrer)) + } + return fn(sigManifestDesc) + }); err != nil { + return err + } + return nil +} + +// FetchSignatureBlob returns signature envelope blob and descriptor given +// signature manifest descriptor +func (c *repositoryClient) FetchSignatureBlob(ctx context.Context, desc ocispec.Descriptor) ([]byte, ocispec.Descriptor, error) { + sigManifest, err := c.getSignatureManifest(ctx, desc) + if err != nil { + return nil, ocispec.Descriptor{}, err + } + if len(sigManifest.Blobs) == 0 { + return nil, ocispec.Descriptor{}, errors.New("signature manifest missing signature envelope blob") + } + sigDesc, err := c.Repository.Blobs().Resolve(ctx, sigManifest.Blobs[0].Digest.String()) + if err != nil { + return nil, ocispec.Descriptor{}, err + } + if sigDesc.Size > maxBlobSizeLimit { + return nil, ocispec.Descriptor{}, fmt.Errorf("signature blob too large: %d", sigDesc.Size) + } + sigBlob, err := content.FetchAll(ctx, c.Repository.Blobs(), sigDesc) + if err != nil { + return nil, ocispec.Descriptor{}, err + } + return sigBlob, sigDesc, nil +} + +// PushSignature creates and uploads an signature manifest along with its +// linked signature envelope blob. Upon successful, PushSignature returns +// signature envelope blob and manifest descriptors. +func (c *repositoryClient) PushSignature(ctx context.Context, blob []byte, mediaType string, subject ocispec.Descriptor, annotations map[string]string) (blobDesc, manifestDesc ocispec.Descriptor, err error) { + blobDesc, err = c.uploadSignature(ctx, blob, mediaType) + if err != nil { + return ocispec.Descriptor{}, ocispec.Descriptor{}, err + } + + manifestDesc, err = c.uploadSignatureManifest(ctx, artifactspecDescriptorFromOCI(subject), artifactspecDescriptorFromOCI(blobDesc), annotations) + if err != nil { + return ocispec.Descriptor{}, ocispec.Descriptor{}, err + } + + return blobDesc, manifestDesc, nil +} + +// getSignatureManifest returns signature manifest given signature manifest +// descriptor +func (c *repositoryClient) getSignatureManifest(ctx context.Context, sigManifestDesc ocispec.Descriptor) (artifactspec.Manifest, error) { + + repo := c.Repository + repo.ManifestMediaTypes = []string{ + artifactspec.MediaTypeArtifactManifest, + } + store := repo.Manifests() + ociDesc, err := store.Resolve(ctx, sigManifestDesc.Digest.String()) + if err != nil { + return artifactspec.Manifest{}, err + } + if ociDesc.Size > maxManifestSizeLimit { + return artifactspec.Manifest{}, fmt.Errorf("manifest too large: %d", ociDesc.Size) + } + manifestJSON, err := content.FetchAll(ctx, store, ociDesc) + if err != nil { + return artifactspec.Manifest{}, err + } + + var sigManifest artifactspec.Manifest + err = json.Unmarshal(manifestJSON, &sigManifest) + if err != nil { + return artifactspec.Manifest{}, err + } + return sigManifest, nil +} + +// uploadSignature uploads the signature envelope blob to the registry +func (c *repositoryClient) uploadSignature(ctx context.Context, blob []byte, mediaType string) (ocispec.Descriptor, error) { + desc := ocispec.Descriptor{ + MediaType: mediaType, + Digest: digest.FromBytes(blob), + Size: int64(len(blob)), + } + if err := c.Repository.Blobs().Push(ctx, desc, bytes.NewReader(blob)); err != nil { + return ocispec.Descriptor{}, err + } + return desc, nil +} + +// uploadSignatureManifest uploads the signature manifest to the registry +func (c *repositoryClient) uploadSignatureManifest(ctx context.Context, subject, blobDesc artifactspec.Descriptor, annotations map[string]string) (ocispec.Descriptor, error) { + opts := oras.PackArtifactOptions{ + Subject: &subject, + ManifestAnnotations: annotations, + } + + manifestDesc, err := oras.PackArtifact(ctx, c.Repository.Manifests(), ArtifactTypeNotation, []artifactspec.Descriptor{blobDesc}, opts) + if err != nil { + return ocispec.Descriptor{}, err + } + return manifestDesc, nil +} + +func ocispecDescriptorFromArtifact(desc artifactspec.Descriptor) ocispec.Descriptor { + return ocispec.Descriptor{ + MediaType: desc.MediaType, + Digest: desc.Digest, + Size: desc.Size, + Annotations: desc.Annotations, + } +} + +func artifactspecDescriptorFromOCI(desc ocispec.Descriptor) artifactspec.Descriptor { + return artifactspec.Descriptor{ + MediaType: desc.MediaType, + Digest: desc.Digest, + Size: desc.Size, + Annotations: desc.Annotations, + } +} diff --git a/registry/repositoryClient_test.go b/registry/repositoryClient_test.go new file mode 100644 index 00000000..993619a2 --- /dev/null +++ b/registry/repositoryClient_test.go @@ -0,0 +1,499 @@ +package registry + +import ( + "bytes" + "context" + "fmt" + "io" + "net/http" + "net/url" + "reflect" + "strings" + "testing" + + "github.com/opencontainers/go-digest" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + artifactspec "github.com/oras-project/artifacts-spec/specs-go/v1" + "oras.land/oras-go/v2/registry" + "oras.land/oras-go/v2/registry/remote" +) + +const ( + validDigest = "6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b" + validDigest2 = "9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0" + validDigest3 = "1834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f2" + validDigest4 = "277000f8d32d2b2a7d65f4533339f7d4c064e0540facf1d54c69d9916f05d28c" + validDigest5 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + validDigest6 = "daffbe5f71beaf7b05c080e8ae4f9739cdf21e24c89561e35792f1251d38148d" + validDigest7 = "13b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + validDigest8 = "57f2c47061dae97063dc46598168a80a9f89302c1f24fe2a422a1ec0aba3017a" + validDigest9 = "023c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b" + validDigest10 = "1761e09cad8aa44e48ffb41c78371a6c139bd0df555c90b5d99739b9551c7828" + invalidDigest = "invaliddigest" + algo = "sha256" + validDigestWithAlgo = algo + ":" + validDigest + validDigestWithAlgo2 = algo + ":" + validDigest2 + validDigestWithAlgo3 = algo + ":" + validDigest3 + validDigestWithAlgo4 = algo + ":" + validDigest4 + validDigestWithAlgo5 = algo + ":" + validDigest5 + validDigestWithAlgo6 = algo + ":" + validDigest6 + validDigestWithAlgo7 = algo + ":" + validDigest7 + validDigestWithAlgo8 = algo + ":" + validDigest8 + validDigestWithAlgo9 = algo + ":" + validDigest9 + validDigestWithAlgo10 = algo + ":" + validDigest10 + validHost = "localhost" + validRegistry = validHost + ":5000" + invalidHost = "badhost" + invalidRegistry = invalidHost + ":5000" + validRepo = "test" + msg = "message" + errMsg = "error message" + mediaType = "application/json" + validReference = validRegistry + "/" + validRepo + "@" + validDigest + referenceWithInvalidHost = invalidRegistry + "/" + validRepo + "@" + validDigest + validReference6 = validRegistry + "/" + validRepo + "@" + validDigest6 + invalidReference = "invalid reference" + joseTag = "application/jose+json" + coseTag = "application/cose" + validTimestamp = "2022-07-29T02:23:10Z" + size = 104 + size2 = 135 + validPage = ` + { + "references": [{}], + "referrers": [ + { + "artifactType": "application/vnd.cncf.notary.v2.signature", + "mediaType": "application/vnd.cncf.oras.artifact.manifest.v1+json", + "digest": "localhost:5000/test@57f2c47061dae97063dc46598168a80a9f89302c1f24fe2a422a1ec0aba3017a" + } + ] + }` + pageWithWrongMediaType = ` + { + "references": [{}], + "referrers": [ + { + "artifactType": "application/vnd.cncf.notary.v2.signature", + "mediaType": "application/vnd.cncf.oras.artifact.manifest.invalid", + "digest": "localhost:5000/test@1834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f2" + } + ] + }` + pageWithBadDigest = ` + { + "references": [{}], + "referrers": [ + { + "artifactType": "application/vnd.cncf.notary.v2.signature", + "mediaType": "application/vnd.cncf.oras.artifact.manifest.v1+json", + "digest": "localhost:5000/test@9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0" + } + ] + }` + validBlob = `{ + "digest": "sha256:6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b", + "size": 90 + }` + validManifest = `{ + "blobs": [ + { + "digest": "sha256:023c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b", + "size": 90 + } + ] + }` +) + +type args struct { + ctx context.Context + reference string + remoteClient remote.Client + plainHttp bool + digest digest.Digest + annotations map[string]string + subjectManifest ocispec.Descriptor + signature []byte + signatureMediaType string + signatureManifestDesc ocispec.Descriptor + artifactManifestDesc ocispec.Descriptor +} + +type mockRemoteClient struct { +} + +func (c mockRemoteClient) Do(req *http.Request) (*http.Response, error) { + switch req.URL.Path { + case "/v2/test/manifests/" + validDigest: + return &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewReader([]byte(msg))), + Header: map[string][]string{ + "Content-Type": {mediaType}, + "Docker-Content-Digest": {validDigestWithAlgo}, + }, + }, nil + case "/v2/test/blobs/" + validDigestWithAlgo6: + return &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewReader([]byte(validBlob))), + ContentLength: size, + Header: map[string][]string{ + "Content-Type": {mediaType}, + "Docker-Content-Digest": {validDigestWithAlgo6}, + }, + }, nil + case "/v2/test/blobs/" + validDigestWithAlgo3: + return &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewReader([]byte(validBlob))), + ContentLength: maxBlobSizeLimit + 1, + Header: map[string][]string{ + "Content-Type": {mediaType}, + "Docker-Content-Digest": {validDigestWithAlgo3}, + }, + }, nil + case "/v2/test/manifests/" + invalidDigest: + return &http.Response{}, fmt.Errorf(errMsg) + case "/v2/test/_oras/artifacts/referrers": + if strings.HasSuffix(req.URL.RawQuery, invalidDigest) { + return &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewReader([]byte(pageWithBadDigest))), + Header: map[string][]string{ + "Oras-Api-Version": {"oras/1.1"}, + }, + Request: &http.Request{ + Method: "GET", + URL: &url.URL{Path: "/v2/test/_oras/artifacts/referrers"}, + }, + }, nil + } else if strings.HasSuffix(req.URL.RawQuery, validDigest7) { + return &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewReader([]byte(pageWithWrongMediaType))), + Header: map[string][]string{ + "Oras-Api-Version": {"oras/1.1"}, + }, + Request: &http.Request{ + Method: "GET", + URL: &url.URL{Path: "/v2/test/_oras/artifacts/referrers"}, + }, + }, nil + } else if strings.HasSuffix(req.URL.RawQuery, validDigest8) { + return &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewReader([]byte(validPage))), + Header: map[string][]string{ + "Oras-Api-Version": {"oras/1.1"}, + }, + Request: &http.Request{ + Method: "GET", + URL: &url.URL{Path: "/v2/test/_oras/artifacts/referrers"}, + }, + }, nil + } + return &http.Response{}, fmt.Errorf(msg) + case "/v2/test/manifests/" + validDigest2: + return &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewReader([]byte(validDigest2))), + ContentLength: size, + Header: map[string][]string{ + "Content-Type": {mediaType}, + "Docker-Content-Digest": {validDigestWithAlgo4}, + }, + }, nil + case "v2/test/manifest/" + validDigest3: + return &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewReader([]byte(validDigest3))), + Header: map[string][]string{ + "Content-Type": {mediaType}, + "Docker-Content-Digest": {validDigestWithAlgo3}, + }, + }, nil + case "/v2/test/manifests/" + validDigest8: + return &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewReader([]byte(validDigest8))), + ContentLength: size2, + Header: map[string][]string{ + "Content-Type": {mediaType}, + "Docker-Content-Digest": {validDigestWithAlgo8}, + }, + }, nil + case "/v2/test/manifests/" + validDigestWithAlgo4: + if req.Method == "GET" { + return &http.Response{}, fmt.Errorf(msg) + } + return &http.Response{ + StatusCode: http.StatusCreated, + Body: io.NopCloser(bytes.NewReader([]byte(msg))), + Header: map[string][]string{ + "Docker-Content-Digest": {validDigestWithAlgo4}, + }, + }, nil + case "/v2/test/manifests/" + validDigestWithAlgo7: + return &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewReader([]byte(msg))), + Header: map[string][]string{ + "Docker-Content-Digest": {validDigestWithAlgo4}, + }, + }, nil + case "/v2/test/manifests/" + validDigestWithAlgo8: + return &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewReader([]byte(validManifest))), + ContentLength: size2, + Header: map[string][]string{ + "Docker-Content-Digest": {validDigestWithAlgo8}, + "Content-Type": {mediaType}, + }, + }, nil + case "/v2/test/manifests/" + validDigestWithAlgo2: + return &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewReader([]byte(validBlob))), + Header: map[string][]string{ + "Docker-Content-Digest": {validDigestWithAlgo2}, + "Content-Type": {mediaType}, + }, + }, nil + case "/v2/test/manifests/" + validDigestWithAlgo10: + if req.Method == "GET" { + return &http.Response{}, fmt.Errorf(msg) + } + return &http.Response{ + StatusCode: http.StatusCreated, + Body: io.NopCloser(bytes.NewReader([]byte(msg))), + Header: map[string][]string{ + "Docker-Content-Digest": {validDigestWithAlgo10}, + }, + }, nil + case "/v2/test/blobs/uploads/": + switch req.Host { + case validRegistry: + return &http.Response{ + StatusCode: http.StatusAccepted, + Body: io.NopCloser(bytes.NewReader([]byte(msg))), + Request: &http.Request{ + Header: map[string][]string{}, + }, + Header: map[string][]string{ + "Location": {"test"}, + }, + }, nil + default: + return &http.Response{}, fmt.Errorf(msg) + } + case validRepo: + return &http.Response{ + StatusCode: http.StatusCreated, + Body: io.NopCloser(bytes.NewReader([]byte(msg))), + }, nil + default: + return &http.Response{}, fmt.Errorf(errMsg) + } +} + +func TestResolve(t *testing.T) { + tests := []struct { + name string + args args + expect ocispec.Descriptor + expectErr bool + }{ + { + name: "failed to resolve", + args: args{ + ctx: context.Background(), + reference: invalidReference, + remoteClient: mockRemoteClient{}, + plainHttp: false, + }, + expect: ocispec.Descriptor{}, + expectErr: true, + }, + { + name: "succeed to resolve", + args: args{ + ctx: context.Background(), + reference: validReference, + remoteClient: mockRemoteClient{}, + plainHttp: false, + }, + expect: ocispec.Descriptor{ + MediaType: mediaType, + Digest: validDigestWithAlgo, + }, + expectErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + args := tt.args + ref, _ := registry.ParseReference(args.reference) + client := newRepositoryClient(args.remoteClient, ref, args.plainHttp) + res, err := client.Resolve(args.ctx, args.reference) + if (err != nil) != tt.expectErr { + t.Errorf("error = %v, expectErr = %v", err, tt.expectErr) + } + if !reflect.DeepEqual(res, tt.expect) { + t.Errorf("expect %+v, got %+v", tt.expect, res) + } + }) + } +} + +func TestFetchSignatureBlob(t *testing.T) { + tests := []struct { + name string + args args + expect []byte + expectErr bool + }{ + { + name: "failed to resolve", + expect: nil, + expectErr: true, + args: args{ + ctx: context.Background(), + reference: validReference, + remoteClient: mockRemoteClient{}, + plainHttp: false, + signatureManifestDesc: ocispec.Descriptor{ + MediaType: artifactspec.MediaTypeArtifactManifest, + Digest: digest.Digest(invalidDigest), + }, + }, + }, + { + name: "exceed max blob size", + expect: nil, + expectErr: true, + args: args{ + ctx: context.Background(), + reference: validReference, + remoteClient: mockRemoteClient{}, + plainHttp: false, + signatureManifestDesc: ocispec.Descriptor{ + MediaType: artifactspec.MediaTypeArtifactManifest, + Digest: digest.Digest(validDigestWithAlgo3), + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + args := tt.args + ref, _ := registry.ParseReference(args.reference) + client := newRepositoryClient(args.remoteClient, ref, args.plainHttp) + res, _, err := client.FetchSignatureBlob(args.ctx, args.signatureManifestDesc) + if (err != nil) != tt.expectErr { + t.Errorf("error = %v, expectErr = %v", err, tt.expectErr) + } + if !reflect.DeepEqual(res, tt.expect) { + t.Errorf("expect %+v, got %+v", tt.expect, res) + } + }) + } +} + +func TestListSignatures(t *testing.T) { + tests := []struct { + name string + args args + expect []interface{} + expectErr bool + }{ + { + name: "failed to fetch content", + expectErr: true, + expect: nil, + args: args{ + ctx: context.Background(), + reference: validReference, + remoteClient: mockRemoteClient{}, + plainHttp: false, + digest: digest.Digest(invalidDigest), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + args := tt.args + ref, _ := registry.ParseReference(args.reference) + client := newRepositoryClient(args.remoteClient, ref, args.plainHttp) + + err := client.ListSignatures(args.ctx, args.artifactManifestDesc, nil) + if (err != nil) != tt.expectErr { + t.Errorf("error = %v, expectErr = %v", err, tt.expectErr) + } + }) + } +} + +func TestPushSignature(t *testing.T) { + tests := []struct { + name string + args args + expectDes ocispec.Descriptor + expectManifest ocispec.Descriptor + expectErr bool + }{ + { + name: "failed to upload signature", + expectErr: true, + expectDes: ocispec.Descriptor{}, + expectManifest: ocispec.Descriptor{}, + args: args{ + reference: referenceWithInvalidHost, + signature: make([]byte, 0), + ctx: context.Background(), + remoteClient: mockRemoteClient{}, + }, + }, + { + name: "failed to upload signature manifest", + expectErr: true, + expectDes: ocispec.Descriptor{}, + expectManifest: ocispec.Descriptor{}, + args: args{ + reference: validReference, + signature: make([]byte, 0), + ctx: context.Background(), + remoteClient: mockRemoteClient{}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + args := tt.args + ref, _ := registry.ParseReference(args.reference) + client := newRepositoryClient(args.remoteClient, ref, args.plainHttp) + + des, _, err := client.PushSignature(args.ctx, args.signature, args.signatureMediaType, args.subjectManifest, args.annotations) + if (err != nil) != tt.expectErr { + t.Errorf("error = %v, expectErr = %v", err, tt.expectErr) + } + if !reflect.DeepEqual(des, tt.expectDes) { + t.Errorf("expect descriptor: %+v, got %+v", tt.expectDes, des) + } + }) + } +} + +// newRepositoryClient creates a new repository client. +func newRepositoryClient(client remote.Client, ref registry.Reference, plainHTTP bool) *repositoryClient { + return &repositoryClient{ + Repository: remote.Repository{ + Client: client, + Reference: ref, + PlainHTTP: plainHTTP, + }, + } +} diff --git a/verification/verifier.go b/verification/verifier.go index 860b4b98..35c46b89 100644 --- a/verification/verifier.go +++ b/verification/verifier.go @@ -7,9 +7,9 @@ import ( "github.com/notaryproject/notation-go" "github.com/notaryproject/notation-go/dir" + "github.com/notaryproject/notation-go/internal/registry" "github.com/notaryproject/notation-go/plugin" "github.com/notaryproject/notation-go/plugin/manager" - "github.com/notaryproject/notation-go/registry" ) type Verifier struct { diff --git a/verification/verifier_helpers.go b/verification/verifier_helpers.go index 90b8e2fd..67557c1d 100644 --- a/verification/verifier_helpers.go +++ b/verification/verifier_helpers.go @@ -10,8 +10,8 @@ import ( "time" "github.com/notaryproject/notation-core-go/signature" + "github.com/notaryproject/notation-go/internal/registry" "github.com/notaryproject/notation-go/plugin" - "github.com/notaryproject/notation-go/registry" sig "github.com/notaryproject/notation-go/signature" ) diff --git a/verification/verifier_test.go b/verification/verifier_test.go index b2b31879..ede639f3 100644 --- a/verification/verifier_test.go +++ b/verification/verifier_test.go @@ -11,9 +11,9 @@ import ( "github.com/notaryproject/notation-go" "github.com/notaryproject/notation-go/dir" "github.com/notaryproject/notation-go/internal/mock" + "github.com/notaryproject/notation-go/internal/registry" "github.com/notaryproject/notation-go/plugin" "github.com/notaryproject/notation-go/plugin/manager" - "github.com/notaryproject/notation-go/registry" _ "github.com/notaryproject/notation-core-go/signature/cose" _ "github.com/notaryproject/notation-core-go/signature/jws" From 0f7afa633f1bbabe6f062ed40d77ee31d13339c4 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Tue, 8 Nov 2022 17:23:36 +0800 Subject: [PATCH 02/10] updated per code review Signed-off-by: Patrick Zheng --- go.mod | 5 +- go.sum | 12 +- internal/registry/repository.go | 32 ++-- registry/interface.go | 24 +++ registry/repository.go | 146 ++++++++++++++-- registry/repositoryClient.go | 163 ------------------ ...itoryClient_test.go => repository_test.go} | 14 -- 7 files changed, 174 insertions(+), 222 deletions(-) create mode 100644 registry/interface.go delete mode 100644 registry/repositoryClient.go rename registry/{repositoryClient_test.go => repository_test.go} (97%) diff --git a/go.mod b/go.mod index 3b61b04a..9f2db601 100644 --- a/go.mod +++ b/go.mod @@ -6,10 +6,10 @@ require ( github.com/go-ldap/ldap/v3 v3.4.4 github.com/notaryproject/notation-core-go v0.2.0-beta.1 github.com/opencontainers/go-digest v1.0.0 - github.com/opencontainers/image-spec v1.0.2 + github.com/opencontainers/image-spec v1.1.0-rc2 github.com/oras-project/artifacts-spec v1.0.0-rc.2 github.com/veraison/go-cose v1.0.0-rc.1.0.20220824135457-9d2fab636b83 - oras.land/oras-go/v2 v2.0.0-rc.3 + oras.land/oras-go/v2 v2.0.0-rc.4 ) require ( @@ -17,7 +17,6 @@ require ( github.com/fxamacker/cbor/v2 v2.4.0 // indirect github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect github.com/golang-jwt/jwt/v4 v4.4.2 // indirect - github.com/opencontainers/distribution-spec/specs-go v0.0.0-20220620172159-4ab4752c3b86 // indirect github.com/x448/float16 v0.8.4 // indirect golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect diff --git a/go.sum b/go.sum index 685070c7..38b7b6f6 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e h1:NeAW1fUYUEWhft7pkxDf6WoUvEZJ/uOKsvtpjLnn8MU= github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88= github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A= @@ -12,12 +12,10 @@ github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQA github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/notaryproject/notation-core-go v0.2.0-beta.1 h1:8tFxNycWCcPLti9ZYST5kjkX2wMXtX9YPvMjiBAQ1tA= github.com/notaryproject/notation-core-go v0.2.0-beta.1/go.mod h1:s8DZptmN1rZS0tBLTPt/w+d4o6eAcGWTYYJlXaJhQ4U= -github.com/opencontainers/distribution-spec/specs-go v0.0.0-20220620172159-4ab4752c3b86 h1:Oumw+lPnO8qNLTY2mrqPJZMoGExLi/0h/DdikoLTXVU= -github.com/opencontainers/distribution-spec/specs-go v0.0.0-20220620172159-4ab4752c3b86/go.mod h1:aA4vdXRS8E1TG7pLZOz85InHi3BiPdErh8IpJN6E0x4= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= -github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= +github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= github.com/oras-project/artifacts-spec v1.0.0-rc.2 h1:9SMCNSxkJEHqWGDiMCuy6TXHgvjgwXGdXZZGXLKQvVE= github.com/oras-project/artifacts-spec v1.0.0-rc.2/go.mod h1:Xch2aLzSwtkhbFFN6LUzTfLtukYvMMdXJ4oZ8O7BOdc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -43,5 +41,5 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -oras.land/oras-go/v2 v2.0.0-rc.3 h1:O4GeIwJ9Ge7rbCkqa/M7DLrL55ww+ZEc+Rhc63OYitU= -oras.land/oras-go/v2 v2.0.0-rc.3/go.mod h1:PrY+cCglzK/DrQoJUtxbYVbL94ZHecVS3eJR01RglpE= +oras.land/oras-go/v2 v2.0.0-rc.4 h1:hg/R2znUQ1+qd43gRmL16VeX1GIZ8hQlLalBjYhhKSk= +oras.land/oras-go/v2 v2.0.0-rc.4/go.mod h1:YGHvWBGuqRlZgUyXUIoKsR3lcuCOb3DAtG0SEsEw1iY= diff --git a/internal/registry/repository.go b/internal/registry/repository.go index 37f38e14..7efb4f5a 100644 --- a/internal/registry/repository.go +++ b/internal/registry/repository.go @@ -55,7 +55,7 @@ func (c *RepositoryClient) ListSignatureManifests(ctx context.Context, manifestD var signatureManifests []SignatureManifest if err := c.Repository.Referrers(ctx, ocispec.Descriptor{ Digest: manifestDigest, - }, ArtifactTypeNotation, func(referrers []artifactspec.Descriptor) error { + }, ArtifactTypeNotation, func(referrers []ocispec.Descriptor) error { for _, desc := range referrers { if desc.MediaType != artifactspec.MediaTypeArtifactManifest { continue @@ -98,13 +98,13 @@ func (c *RepositoryClient) PutSignatureManifest(ctx context.Context, signature [ return notation.Descriptor{}, SignatureManifest{}, err } - manifestDesc, err := c.uploadSignatureManifest(ctx, artifactDescriptorFromNotation(subjectManifest), signatureDesc, annotations) + manifestDesc, err := c.uploadSignatureManifest(ctx, ociDescriptorFromNotation(subjectManifest), signatureDesc, annotations) if err != nil { return notation.Descriptor{}, SignatureManifest{}, err } signatureManifest := SignatureManifest{ - Blob: notationDescriptorFromArtifact(signatureDesc), + Blob: notationDescriptorFromOCI(signatureDesc), Annotations: annotations, } return notationDescriptorFromOCI(manifestDesc), signatureManifest, nil @@ -137,36 +137,36 @@ func (c *RepositoryClient) getArtifactManifest(ctx context.Context, manifestDige } // uploadSignature uploads the signature to the registry -func (c *RepositoryClient) uploadSignature(ctx context.Context, signature []byte, signatureMediaType string) (artifactspec.Descriptor, error) { +func (c *RepositoryClient) uploadSignature(ctx context.Context, signature []byte, signatureMediaType string) (ocispec.Descriptor, error) { desc := ocispec.Descriptor{ MediaType: signatureMediaType, Digest: digest.FromBytes(signature), Size: int64(len(signature)), } if err := c.Repository.Blobs().Push(ctx, desc, bytes.NewReader(signature)); err != nil { - return artifactspec.Descriptor{}, err + return ocispec.Descriptor{}, err } - return artifactDescriptorFromOCI(desc), nil + return desc, nil } // uploadSignatureManifest uploads the signature manifest to the registry -func (c *RepositoryClient) uploadSignatureManifest(ctx context.Context, subjectManifest, signatureDesc artifactspec.Descriptor, annotations map[string]string) (ocispec.Descriptor, error) { - opts := oras.PackArtifactOptions{ +func (c *RepositoryClient) uploadSignatureManifest(ctx context.Context, subjectManifest, signatureDesc ocispec.Descriptor, annotations map[string]string) (ocispec.Descriptor, error) { + opts := oras.PackOptions{ Subject: &subjectManifest, ManifestAnnotations: annotations, } - return oras.PackArtifact( + return oras.Pack( ctx, c.Repository.Manifests(), ArtifactTypeNotation, - []artifactspec.Descriptor{signatureDesc}, + []ocispec.Descriptor{signatureDesc}, opts, ) } -func artifactDescriptorFromNotation(desc notation.Descriptor) artifactspec.Descriptor { - return artifactspec.Descriptor{ +func ociDescriptorFromNotation(desc notation.Descriptor) ocispec.Descriptor { + return ocispec.Descriptor{ MediaType: desc.MediaType, Digest: desc.Digest, Size: desc.Size, @@ -181,14 +181,6 @@ func notationDescriptorFromArtifact(desc artifactspec.Descriptor) notation.Descr } } -func artifactDescriptorFromOCI(desc ocispec.Descriptor) artifactspec.Descriptor { - return artifactspec.Descriptor{ - MediaType: desc.MediaType, - Digest: desc.Digest, - Size: desc.Size, - } -} - func notationDescriptorFromOCI(desc ocispec.Descriptor) notation.Descriptor { return notation.Descriptor{ MediaType: desc.MediaType, diff --git a/registry/interface.go b/registry/interface.go new file mode 100644 index 00000000..27de5965 --- /dev/null +++ b/registry/interface.go @@ -0,0 +1,24 @@ +// Package registry provides Repository for remote signing and verification +package registry + +import ( + "context" + + ocispec "github.com/opencontainers/image-spec/specs-go/v1" +) + +// Repository provides registry functionalities for remote signing and +// verification. +type Repository interface { + // Resolve resolves a reference(tag or digest) to a manifest descriptor + Resolve(ctx context.Context, reference string) (ocispec.Descriptor, error) + // ListSignatures returns signature manifests filtered by fn given the + // artifact manifest descriptor + ListSignatures(ctx context.Context, desc ocispec.Descriptor, fn func(signatureManifests []ocispec.Descriptor) error) error + // FetchSignatureBlob returns signature envelope blob and descriptor given + // signature manifest descriptor + FetchSignatureBlob(ctx context.Context, desc ocispec.Descriptor) ([]byte, ocispec.Descriptor, error) + // PushSignature creates and uploads an signature manifest along with its + // linked signature envelope blob. + PushSignature(ctx context.Context, blob []byte, mediaType string, subject ocispec.Descriptor, annotations map[string]string) (blobDesc, manifestDesc ocispec.Descriptor, err error) +} diff --git a/registry/repository.go b/registry/repository.go index 7406e2eb..b6b91b0a 100644 --- a/registry/repository.go +++ b/registry/repository.go @@ -1,27 +1,28 @@ -// Package registry provides Repository for remote signing and verification package registry import ( + "bytes" "context" + "encoding/json" + "errors" + "fmt" + "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" + artifactspec "github.com/oras-project/artifacts-spec/specs-go/v1" + "oras.land/oras-go/v2" + "oras.land/oras-go/v2/content" "oras.land/oras-go/v2/registry/remote" ) -// Repository provides registry functionalities for remote signing and -// verification. -type Repository interface { - // Resolve resolves a reference(tag or digest) to a manifest descriptor - Resolve(ctx context.Context, reference string) (ocispec.Descriptor, error) - // ListSignatures returns signature manifests filtered by fn given the - // artifact manifest descriptor - ListSignatures(ctx context.Context, desc ocispec.Descriptor, fn func(signatureManifests []ocispec.Descriptor) error) error - // FetchSignatureBlob returns signature envelope blob and descriptor given - // signature manifest descriptor - FetchSignatureBlob(ctx context.Context, desc ocispec.Descriptor) ([]byte, ocispec.Descriptor, error) - // PushSignature creates and uploads an signature manifest along with its - // linked signature envelope blob. - PushSignature(ctx context.Context, blob []byte, mediaType string, subject ocispec.Descriptor, annotations map[string]string) (blobDesc, manifestDesc ocispec.Descriptor, err error) +const ( + maxBlobSizeLimit = 32 * 1024 * 1024 // 32 MiB + maxManifestSizeLimit = 4 * 1024 * 1024 // 4 MiB +) + +// repositoryClient implements Repository +type repositoryClient struct { + remote.Repository } // NewRepository returns a new Repository @@ -30,3 +31,118 @@ func NewRepository(repo remote.Repository) Repository { Repository: repo, } } + +// Resolve resolves a reference(tag or digest) to a manifest descriptor +func (c *repositoryClient) Resolve(ctx context.Context, reference string) (ocispec.Descriptor, error) { + return c.Repository.Resolve(ctx, reference) +} + +// ListSignatures returns signature manifests filtered by fn given the +// artifact manifest descriptor +func (c *repositoryClient) ListSignatures(ctx context.Context, desc ocispec.Descriptor, fn func(signatureManifests []ocispec.Descriptor) error) error { + return c.Repository.Referrers(ctx, ocispec.Descriptor{ + Digest: desc.Digest, + }, ArtifactTypeNotation, func(referrers []ocispec.Descriptor) error { + var sigManifestDesc []ocispec.Descriptor + sigManifestDesc = append(sigManifestDesc, referrers...) + return fn(sigManifestDesc) + }) +} + +// FetchSignatureBlob returns signature envelope blob and descriptor given +// signature manifest descriptor +func (c *repositoryClient) FetchSignatureBlob(ctx context.Context, desc ocispec.Descriptor) ([]byte, ocispec.Descriptor, error) { + sigManifest, err := c.getSignatureManifest(ctx, desc) + if err != nil { + return nil, ocispec.Descriptor{}, err + } + if len(sigManifest.Blobs) == 0 { + return nil, ocispec.Descriptor{}, errors.New("signature manifest missing signature envelope blob") + } + sigDesc := ociDescriptorFromArtifact(sigManifest.Blobs[0]) + if sigDesc.Size > maxBlobSizeLimit { + return nil, ocispec.Descriptor{}, fmt.Errorf("signature blob too large: %d", sigDesc.Size) + } + sigBlob, err := content.FetchAll(ctx, c.Repository.Blobs(), sigDesc) + if err != nil { + return nil, ocispec.Descriptor{}, err + } + return sigBlob, sigDesc, nil +} + +// PushSignature creates and uploads an signature manifest along with its +// linked signature envelope blob. Upon successful, PushSignature returns +// signature envelope blob and manifest descriptors. +func (c *repositoryClient) PushSignature(ctx context.Context, blob []byte, mediaType string, subject ocispec.Descriptor, annotations map[string]string) (blobDesc, manifestDesc ocispec.Descriptor, err error) { + blobDesc, err = c.uploadSignature(ctx, blob, mediaType) + if err != nil { + return ocispec.Descriptor{}, ocispec.Descriptor{}, err + } + + manifestDesc, err = c.uploadSignatureManifest(ctx, subject, blobDesc, annotations) + if err != nil { + return ocispec.Descriptor{}, ocispec.Descriptor{}, err + } + + return blobDesc, manifestDesc, nil +} + +// getSignatureManifest returns signature manifest given signature manifest +// descriptor +func (c *repositoryClient) getSignatureManifest(ctx context.Context, sigManifestDesc ocispec.Descriptor) (*artifactspec.Manifest, error) { + + repo := c.Repository + repo.ManifestMediaTypes = []string{ + artifactspec.MediaTypeArtifactManifest, + } + store := repo.Manifests() + if sigManifestDesc.Size > maxManifestSizeLimit { + return &artifactspec.Manifest{}, fmt.Errorf("manifest too large: %d", sigManifestDesc.Size) + } + manifestJSON, err := content.FetchAll(ctx, store, sigManifestDesc) + if err != nil { + return &artifactspec.Manifest{}, err + } + + var sigManifest artifactspec.Manifest + err = json.Unmarshal(manifestJSON, &sigManifest) + if err != nil { + return &artifactspec.Manifest{}, err + } + return &sigManifest, nil +} + +// uploadSignature uploads the signature envelope blob to the registry +func (c *repositoryClient) uploadSignature(ctx context.Context, blob []byte, mediaType string) (ocispec.Descriptor, error) { + desc := ocispec.Descriptor{ + MediaType: mediaType, + Digest: digest.FromBytes(blob), + Size: int64(len(blob)), + } + if err := c.Repository.Blobs().Push(ctx, desc, bytes.NewReader(blob)); err != nil { + return ocispec.Descriptor{}, err + } + return desc, nil +} + +// uploadSignatureManifest uploads the signature manifest to the registry +func (c *repositoryClient) uploadSignatureManifest(ctx context.Context, subject, blobDesc ocispec.Descriptor, annotations map[string]string) (ocispec.Descriptor, error) { + opts := oras.PackOptions{ + Subject: &subject, + ManifestAnnotations: annotations, + } + + manifestDesc, err := oras.Pack(ctx, c.Repository.Manifests(), ArtifactTypeNotation, []ocispec.Descriptor{blobDesc}, opts) + if err != nil { + return ocispec.Descriptor{}, err + } + return manifestDesc, nil +} + +func ociDescriptorFromArtifact(desc artifactspec.Descriptor) ocispec.Descriptor { + return ocispec.Descriptor{ + MediaType: desc.MediaType, + Digest: desc.Digest, + Size: desc.Size, + } +} diff --git a/registry/repositoryClient.go b/registry/repositoryClient.go deleted file mode 100644 index 806cd68d..00000000 --- a/registry/repositoryClient.go +++ /dev/null @@ -1,163 +0,0 @@ -package registry - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - - "github.com/opencontainers/go-digest" - ocispec "github.com/opencontainers/image-spec/specs-go/v1" - artifactspec "github.com/oras-project/artifacts-spec/specs-go/v1" - "oras.land/oras-go/v2" - "oras.land/oras-go/v2/content" - "oras.land/oras-go/v2/registry/remote" -) - -const ( - maxBlobSizeLimit = 32 * 1024 * 1024 // 32 MiB - maxManifestSizeLimit = 4 * 1024 * 1024 // 4 MiB -) - -// repositoryClient implements Repository -type repositoryClient struct { - remote.Repository -} - -// Resolve resolves a reference(tag or digest) to a manifest descriptor -func (c *repositoryClient) Resolve(ctx context.Context, reference string) (ocispec.Descriptor, error) { - return c.Repository.Resolve(ctx, reference) -} - -// ListSignatures returns signature manifests filtered by fn given the -// artifact manifest descriptor -func (c *repositoryClient) ListSignatures(ctx context.Context, desc ocispec.Descriptor, fn func(signatureManifests []ocispec.Descriptor) error) error { - if err := c.Repository.Referrers(ctx, ocispec.Descriptor{ - Digest: desc.Digest, - }, ArtifactTypeNotation, func(referrers []artifactspec.Descriptor) error { - var sigManifestDesc []ocispec.Descriptor - for _, referrer := range referrers { - sigManifestDesc = append(sigManifestDesc, ocispecDescriptorFromArtifact(referrer)) - } - return fn(sigManifestDesc) - }); err != nil { - return err - } - return nil -} - -// FetchSignatureBlob returns signature envelope blob and descriptor given -// signature manifest descriptor -func (c *repositoryClient) FetchSignatureBlob(ctx context.Context, desc ocispec.Descriptor) ([]byte, ocispec.Descriptor, error) { - sigManifest, err := c.getSignatureManifest(ctx, desc) - if err != nil { - return nil, ocispec.Descriptor{}, err - } - if len(sigManifest.Blobs) == 0 { - return nil, ocispec.Descriptor{}, errors.New("signature manifest missing signature envelope blob") - } - sigDesc, err := c.Repository.Blobs().Resolve(ctx, sigManifest.Blobs[0].Digest.String()) - if err != nil { - return nil, ocispec.Descriptor{}, err - } - if sigDesc.Size > maxBlobSizeLimit { - return nil, ocispec.Descriptor{}, fmt.Errorf("signature blob too large: %d", sigDesc.Size) - } - sigBlob, err := content.FetchAll(ctx, c.Repository.Blobs(), sigDesc) - if err != nil { - return nil, ocispec.Descriptor{}, err - } - return sigBlob, sigDesc, nil -} - -// PushSignature creates and uploads an signature manifest along with its -// linked signature envelope blob. Upon successful, PushSignature returns -// signature envelope blob and manifest descriptors. -func (c *repositoryClient) PushSignature(ctx context.Context, blob []byte, mediaType string, subject ocispec.Descriptor, annotations map[string]string) (blobDesc, manifestDesc ocispec.Descriptor, err error) { - blobDesc, err = c.uploadSignature(ctx, blob, mediaType) - if err != nil { - return ocispec.Descriptor{}, ocispec.Descriptor{}, err - } - - manifestDesc, err = c.uploadSignatureManifest(ctx, artifactspecDescriptorFromOCI(subject), artifactspecDescriptorFromOCI(blobDesc), annotations) - if err != nil { - return ocispec.Descriptor{}, ocispec.Descriptor{}, err - } - - return blobDesc, manifestDesc, nil -} - -// getSignatureManifest returns signature manifest given signature manifest -// descriptor -func (c *repositoryClient) getSignatureManifest(ctx context.Context, sigManifestDesc ocispec.Descriptor) (artifactspec.Manifest, error) { - - repo := c.Repository - repo.ManifestMediaTypes = []string{ - artifactspec.MediaTypeArtifactManifest, - } - store := repo.Manifests() - ociDesc, err := store.Resolve(ctx, sigManifestDesc.Digest.String()) - if err != nil { - return artifactspec.Manifest{}, err - } - if ociDesc.Size > maxManifestSizeLimit { - return artifactspec.Manifest{}, fmt.Errorf("manifest too large: %d", ociDesc.Size) - } - manifestJSON, err := content.FetchAll(ctx, store, ociDesc) - if err != nil { - return artifactspec.Manifest{}, err - } - - var sigManifest artifactspec.Manifest - err = json.Unmarshal(manifestJSON, &sigManifest) - if err != nil { - return artifactspec.Manifest{}, err - } - return sigManifest, nil -} - -// uploadSignature uploads the signature envelope blob to the registry -func (c *repositoryClient) uploadSignature(ctx context.Context, blob []byte, mediaType string) (ocispec.Descriptor, error) { - desc := ocispec.Descriptor{ - MediaType: mediaType, - Digest: digest.FromBytes(blob), - Size: int64(len(blob)), - } - if err := c.Repository.Blobs().Push(ctx, desc, bytes.NewReader(blob)); err != nil { - return ocispec.Descriptor{}, err - } - return desc, nil -} - -// uploadSignatureManifest uploads the signature manifest to the registry -func (c *repositoryClient) uploadSignatureManifest(ctx context.Context, subject, blobDesc artifactspec.Descriptor, annotations map[string]string) (ocispec.Descriptor, error) { - opts := oras.PackArtifactOptions{ - Subject: &subject, - ManifestAnnotations: annotations, - } - - manifestDesc, err := oras.PackArtifact(ctx, c.Repository.Manifests(), ArtifactTypeNotation, []artifactspec.Descriptor{blobDesc}, opts) - if err != nil { - return ocispec.Descriptor{}, err - } - return manifestDesc, nil -} - -func ocispecDescriptorFromArtifact(desc artifactspec.Descriptor) ocispec.Descriptor { - return ocispec.Descriptor{ - MediaType: desc.MediaType, - Digest: desc.Digest, - Size: desc.Size, - Annotations: desc.Annotations, - } -} - -func artifactspecDescriptorFromOCI(desc ocispec.Descriptor) artifactspec.Descriptor { - return artifactspec.Descriptor{ - MediaType: desc.MediaType, - Digest: desc.Digest, - Size: desc.Size, - Annotations: desc.Annotations, - } -} diff --git a/registry/repositoryClient_test.go b/registry/repository_test.go similarity index 97% rename from registry/repositoryClient_test.go rename to registry/repository_test.go index 993619a2..9ab60c64 100644 --- a/registry/repositoryClient_test.go +++ b/registry/repository_test.go @@ -316,20 +316,6 @@ func TestResolve(t *testing.T) { expect: ocispec.Descriptor{}, expectErr: true, }, - { - name: "succeed to resolve", - args: args{ - ctx: context.Background(), - reference: validReference, - remoteClient: mockRemoteClient{}, - plainHttp: false, - }, - expect: ocispec.Descriptor{ - MediaType: mediaType, - Digest: validDigestWithAlgo, - }, - expectErr: false, - }, } for _, tt := range tests { From 08ad3c1ed7be1865d6a36edd6ab144b4a5e4448f Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Tue, 8 Nov 2022 17:33:59 +0800 Subject: [PATCH 03/10] update Signed-off-by: Patrick Zheng --- internal/registry/repository_test.go | 599 --------------------------- 1 file changed, 599 deletions(-) delete mode 100644 internal/registry/repository_test.go diff --git a/internal/registry/repository_test.go b/internal/registry/repository_test.go deleted file mode 100644 index 97917b2b..00000000 --- a/internal/registry/repository_test.go +++ /dev/null @@ -1,599 +0,0 @@ -package registry - -import ( - "bytes" - "context" - "fmt" - "io" - "net/http" - "net/url" - "reflect" - "strings" - "testing" - - "github.com/notaryproject/notation-go" - "github.com/opencontainers/go-digest" - artifactspec "github.com/oras-project/artifacts-spec/specs-go/v1" - "oras.land/oras-go/v2/registry" - "oras.land/oras-go/v2/registry/remote" -) - -const ( - validDigest = "6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b" - validDigest2 = "9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0" - validDigest3 = "1834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f2" - validDigest4 = "277000f8d32d2b2a7d65f4533339f7d4c064e0540facf1d54c69d9916f05d28c" - validDigest5 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" - validDigest6 = "daffbe5f71beaf7b05c080e8ae4f9739cdf21e24c89561e35792f1251d38148d" - validDigest7 = "13b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" - validDigest8 = "57f2c47061dae97063dc46598168a80a9f89302c1f24fe2a422a1ec0aba3017a" - validDigest9 = "023c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b" - validDigest10 = "1761e09cad8aa44e48ffb41c78371a6c139bd0df555c90b5d99739b9551c7828" - invalidDigest = "invaliddigest" - algo = "sha256" - validDigestWithAlgo = algo + ":" + validDigest - validDigestWithAlgo2 = algo + ":" + validDigest2 - validDigestWithAlgo3 = algo + ":" + validDigest3 - validDigestWithAlgo4 = algo + ":" + validDigest4 - validDigestWithAlgo5 = algo + ":" + validDigest5 - validDigestWithAlgo6 = algo + ":" + validDigest6 - validDigestWithAlgo7 = algo + ":" + validDigest7 - validDigestWithAlgo8 = algo + ":" + validDigest8 - validDigestWithAlgo9 = algo + ":" + validDigest9 - validDigestWithAlgo10 = algo + ":" + validDigest10 - validHost = "localhost" - validRegistry = validHost + ":5000" - invalidHost = "badhost" - invalidRegistry = invalidHost + ":5000" - validRepo = "test" - msg = "message" - errMsg = "error message" - mediaType = "application/json" - validReference = validRegistry + "/" + validRepo + "@" + validDigest - referenceWithInvalidHost = invalidRegistry + "/" + validRepo + "@" + validDigest - validReference6 = validRegistry + "/" + validRepo + "@" + validDigest6 - invalidReference = "invalid reference" - joseTag = "application/jose+json" - coseTag = "application/cose" - validTimestamp = "2022-07-29T02:23:10Z" - size = 104 - size2 = 135 - validPage = ` - { - "references": [{}], - "referrers": [ - { - "artifactType": "application/vnd.cncf.notary.v2.signature", - "mediaType": "application/vnd.cncf.oras.artifact.manifest.v1+json", - "digest": "localhost:5000/test@57f2c47061dae97063dc46598168a80a9f89302c1f24fe2a422a1ec0aba3017a" - } - ] - }` - pageWithWrongMediaType = ` - { - "references": [{}], - "referrers": [ - { - "artifactType": "application/vnd.cncf.notary.v2.signature", - "mediaType": "application/vnd.cncf.oras.artifact.manifest.invalid", - "digest": "localhost:5000/test@1834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f2" - } - ] - }` - pageWithBadDigest = ` - { - "references": [{}], - "referrers": [ - { - "artifactType": "application/vnd.cncf.notary.v2.signature", - "mediaType": "application/vnd.cncf.oras.artifact.manifest.v1+json", - "digest": "localhost:5000/test@9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0" - } - ] - }` - validBlob = `{ - "digest": "sha256:6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b", - "size": 90 - }` - validManifest = `{ - "blobs": [ - { - "digest": "sha256:023c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b", - "size": 90 - } - ] - }` -) - -type args struct { - ctx context.Context - reference string - remoteClient remote.Client - plainHttp bool - digest digest.Digest - annotations map[string]string - subjectManifest notation.Descriptor - signature []byte - signatureMediaType string -} - -type mockRemoteClient struct { -} - -func (c mockRemoteClient) Do(req *http.Request) (*http.Response, error) { - switch req.URL.Path { - case "/v2/test/manifests/" + validDigest: - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader([]byte(msg))), - Header: map[string][]string{ - "Content-Type": {mediaType}, - "Docker-Content-Digest": {validDigestWithAlgo}, - }, - }, nil - case "/v2/test/blobs/" + validDigestWithAlgo6: - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader([]byte(validBlob))), - ContentLength: size, - Header: map[string][]string{ - "Content-Type": {mediaType}, - "Docker-Content-Digest": {validDigestWithAlgo6}, - }, - }, nil - case "/v2/test/blobs/" + validDigestWithAlgo3: - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader([]byte(validBlob))), - ContentLength: maxBlobSizeLimit + 1, - Header: map[string][]string{ - "Content-Type": {mediaType}, - "Docker-Content-Digest": {validDigestWithAlgo3}, - }, - }, nil - case "/v2/test/manifests/" + invalidDigest: - return &http.Response{}, fmt.Errorf(errMsg) - case "/v2/test/_oras/artifacts/referrers": - if strings.HasSuffix(req.URL.RawQuery, invalidDigest) { - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader([]byte(pageWithBadDigest))), - Header: map[string][]string{ - "Oras-Api-Version": {"oras/1.1"}, - }, - Request: &http.Request{ - Method: "GET", - URL: &url.URL{Path: "/v2/test/_oras/artifacts/referrers"}, - }, - }, nil - } else if strings.HasSuffix(req.URL.RawQuery, validDigest7) { - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader([]byte(pageWithWrongMediaType))), - Header: map[string][]string{ - "Oras-Api-Version": {"oras/1.1"}, - }, - Request: &http.Request{ - Method: "GET", - URL: &url.URL{Path: "/v2/test/_oras/artifacts/referrers"}, - }, - }, nil - } else if strings.HasSuffix(req.URL.RawQuery, validDigest8) { - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader([]byte(validPage))), - Header: map[string][]string{ - "Oras-Api-Version": {"oras/1.1"}, - }, - Request: &http.Request{ - Method: "GET", - URL: &url.URL{Path: "/v2/test/_oras/artifacts/referrers"}, - }, - }, nil - } - return &http.Response{}, fmt.Errorf(msg) - case "/v2/test/manifests/" + validDigest2: - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader([]byte(validDigest2))), - ContentLength: size, - Header: map[string][]string{ - "Content-Type": {mediaType}, - "Docker-Content-Digest": {validDigestWithAlgo4}, - }, - }, nil - case "v2/test/manifest/" + validDigest3: - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader([]byte(validDigest3))), - Header: map[string][]string{ - "Content-Type": {mediaType}, - "Docker-Content-Digest": {validDigestWithAlgo3}, - }, - }, nil - case "/v2/test/manifests/" + validDigest8: - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader([]byte(validDigest8))), - ContentLength: size2, - Header: map[string][]string{ - "Content-Type": {mediaType}, - "Docker-Content-Digest": {validDigestWithAlgo8}, - }, - }, nil - case "/v2/test/manifests/" + validDigestWithAlgo4: - if req.Method == "GET" { - return &http.Response{}, fmt.Errorf(msg) - } - return &http.Response{ - StatusCode: http.StatusCreated, - Body: io.NopCloser(bytes.NewReader([]byte(msg))), - Header: map[string][]string{ - "Docker-Content-Digest": {validDigestWithAlgo4}, - }, - }, nil - case "/v2/test/manifests/" + validDigestWithAlgo7: - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader([]byte(msg))), - Header: map[string][]string{ - "Docker-Content-Digest": {validDigestWithAlgo4}, - }, - }, nil - case "/v2/test/manifests/" + validDigestWithAlgo8: - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader([]byte(validManifest))), - ContentLength: size2, - Header: map[string][]string{ - "Docker-Content-Digest": {validDigestWithAlgo8}, - "Content-Type": {mediaType}, - }, - }, nil - case "/v2/test/manifests/" + validDigestWithAlgo2: - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewReader([]byte(validBlob))), - Header: map[string][]string{ - "Docker-Content-Digest": {validDigestWithAlgo2}, - "Content-Type": {mediaType}, - }, - }, nil - case "/v2/test/manifests/" + validDigestWithAlgo10: - if req.Method == "GET" { - return &http.Response{}, fmt.Errorf(msg) - } - return &http.Response{ - StatusCode: http.StatusCreated, - Body: io.NopCloser(bytes.NewReader([]byte(msg))), - Header: map[string][]string{ - "Docker-Content-Digest": {validDigestWithAlgo10}, - }, - }, nil - case "/v2/test/blobs/uploads/": - switch req.Host { - case validRegistry: - return &http.Response{ - StatusCode: http.StatusAccepted, - Body: io.NopCloser(bytes.NewReader([]byte(msg))), - Request: &http.Request{ - Header: map[string][]string{}, - }, - Header: map[string][]string{ - "Location": {"test"}, - }, - }, nil - default: - return &http.Response{}, fmt.Errorf(msg) - } - case validRepo: - return &http.Response{ - StatusCode: http.StatusCreated, - Body: io.NopCloser(bytes.NewReader([]byte(msg))), - }, nil - default: - return &http.Response{}, fmt.Errorf(errMsg) - } -} - -func TestNewRepositoryClient_constructCorrectly(t *testing.T) { - remoteClient := mockRemoteClient{} - ref := registry.Reference{} - plainHttp := false - client := NewRepositoryClient(remoteClient, ref, plainHttp) - - if client.Client != remoteClient || - client.PlainHTTP != plainHttp || - client.Reference != ref { - t.Fatalf("Expect client with remoteClient: %v, plainHTTP: %v, ref: %v, got: %+v", - remoteClient, plainHttp, ref, client.Client) - } -} - -func TestResolve(t *testing.T) { - tests := []struct { - name string - args args - expect notation.Descriptor - expectErr bool - }{ - { - name: "failed to resolve", - args: args{ - ctx: context.Background(), - reference: invalidReference, - remoteClient: mockRemoteClient{}, - plainHttp: false, - }, - expect: notation.Descriptor{}, - expectErr: true, - }, - { - name: "succeed to resolve", - args: args{ - ctx: context.Background(), - reference: validReference, - remoteClient: mockRemoteClient{}, - plainHttp: false, - }, - expect: notation.Descriptor{ - MediaType: mediaType, - Digest: validDigestWithAlgo, - }, - expectErr: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - args := tt.args - ref, _ := registry.ParseReference(args.reference) - client := NewRepositoryClient(args.remoteClient, ref, args.plainHttp) - res, err := client.Resolve(args.ctx, args.reference) - if (err != nil) != tt.expectErr { - t.Errorf("error = %v, expectErr = %v", err, tt.expectErr) - } - if !reflect.DeepEqual(res, tt.expect) { - t.Errorf("expect %+v, got %+v", tt.expect, res) - } - }) - } -} - -func TestGetBlob(t *testing.T) { - tests := []struct { - name string - args args - expect []byte - expectErr bool - }{ - { - name: "failed to resolve", - expect: nil, - expectErr: true, - args: args{ - ctx: context.Background(), - reference: validReference, - remoteClient: mockRemoteClient{}, - plainHttp: false, - digest: digest.Digest(invalidDigest), - }, - }, - { - name: "exceed max blob size", - expect: nil, - expectErr: true, - args: args{ - ctx: context.Background(), - reference: validReference, - remoteClient: mockRemoteClient{}, - plainHttp: false, - digest: digest.Digest(validDigestWithAlgo3), - }, - }, - { - name: "succeed to get", - expect: []byte(validBlob), - expectErr: false, - args: args{ - ctx: context.Background(), - reference: validReference6, - remoteClient: mockRemoteClient{}, - plainHttp: false, - digest: digest.Digest(validDigestWithAlgo6), - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - args := tt.args - ref, _ := registry.ParseReference(args.reference) - client := NewRepositoryClient(args.remoteClient, ref, args.plainHttp) - res, err := client.GetBlob(args.ctx, args.digest) - if (err != nil) != tt.expectErr { - t.Errorf("error = %v, expectErr = %v", err, tt.expectErr) - } - if !reflect.DeepEqual(res, tt.expect) { - t.Errorf("expect %+v, got %+v", tt.expect, res) - } - }) - } -} - -func TestListSignatureManifests(t *testing.T) { - tests := []struct { - name string - args args - expect []SignatureManifest - expectErr bool - }{ - { - name: "wrong mediaType", - expectErr: false, - expect: nil, - args: args{ - ctx: context.Background(), - reference: validReference, - remoteClient: mockRemoteClient{}, - plainHttp: false, - digest: digest.Digest(validDigest7), - }, - }, - { - name: "failed to fetch content", - expectErr: true, - expect: nil, - args: args{ - ctx: context.Background(), - reference: validReference, - remoteClient: mockRemoteClient{}, - plainHttp: false, - digest: digest.Digest(invalidDigest), - }, - }, - { - name: "succeed to list", - expectErr: false, - expect: []SignatureManifest{ - { - Blob: notation.Descriptor{ - Digest: validDigestWithAlgo9, - Size: 90, - }, - }, - }, - args: args{ - ctx: context.Background(), - reference: validReference, - remoteClient: mockRemoteClient{}, - plainHttp: false, - digest: digest.Digest(validDigest8), - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - args := tt.args - ref, _ := registry.ParseReference(args.reference) - client := NewRepositoryClient(args.remoteClient, ref, args.plainHttp) - - res, err := client.ListSignatureManifests(args.ctx, args.digest) - if (err != nil) != tt.expectErr { - t.Errorf("error = %v, expectErr = %v", err, tt.expectErr) - } - if !reflect.DeepEqual(res, tt.expect) { - t.Errorf("expect %+v, got %+v", tt.expect, res) - } - }) - } -} - -func TestPutSignatureManifest(t *testing.T) { - tests := []struct { - name string - args args - expectDes notation.Descriptor - expectManifest SignatureManifest - expectErr bool - }{ - { - name: "failed to upload signature", - expectErr: true, - expectDes: notation.Descriptor{}, - expectManifest: SignatureManifest{}, - args: args{ - reference: referenceWithInvalidHost, - signature: make([]byte, 0), - ctx: context.Background(), - remoteClient: mockRemoteClient{}, - }, - }, - { - name: "failed to upload signature manifest", - expectErr: true, - expectDes: notation.Descriptor{}, - expectManifest: SignatureManifest{}, - args: args{ - reference: validReference, - signature: make([]byte, 0), - ctx: context.Background(), - remoteClient: mockRemoteClient{}, - }, - }, - { - name: "succeed to put signature manifest with jws media type", - expectErr: false, - expectDes: notation.Descriptor{ - MediaType: artifactspec.MediaTypeArtifactManifest, - Digest: digest.Digest(validDigestWithAlgo4), - Size: 369, - }, - expectManifest: SignatureManifest{ - Annotations: map[string]string{ - artifactspec.AnnotationArtifactCreated: validTimestamp, - }, - Blob: notation.Descriptor{ - MediaType: joseTag, - Digest: validDigestWithAlgo5, - }, - }, - args: args{ - reference: validReference, - signature: make([]byte, 0), - ctx: context.Background(), - remoteClient: mockRemoteClient{}, - annotations: map[string]string{ - artifactspec.AnnotationArtifactCreated: validTimestamp, - }, - signatureMediaType: joseTag, - }, - }, - { - name: "succeed to put signature manifest with cose media type", - expectErr: false, - expectDes: notation.Descriptor{ - MediaType: artifactspec.MediaTypeArtifactManifest, - Digest: digest.Digest(validDigestWithAlgo10), - Size: 364, - }, - expectManifest: SignatureManifest{ - Annotations: map[string]string{ - artifactspec.AnnotationArtifactCreated: validTimestamp, - }, - Blob: notation.Descriptor{ - MediaType: coseTag, - Digest: validDigestWithAlgo5, - }, - }, - args: args{ - reference: validReference, - signature: make([]byte, 0), - ctx: context.Background(), - remoteClient: mockRemoteClient{}, - annotations: map[string]string{ - artifactspec.AnnotationArtifactCreated: validTimestamp, - }, - signatureMediaType: coseTag, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - args := tt.args - ref, _ := registry.ParseReference(args.reference) - client := NewRepositoryClient(args.remoteClient, ref, args.plainHttp) - - des, manifest, err := client.PutSignatureManifest(args.ctx, args.signature, args.signatureMediaType, args.subjectManifest, args.annotations) - if (err != nil) != tt.expectErr { - t.Errorf("error = %v, expectErr = %v", err, tt.expectErr) - } - if !reflect.DeepEqual(des, tt.expectDes) { - t.Errorf("expect descriptor: %+v, got %+v", tt.expectDes, des) - } - if !reflect.DeepEqual(manifest, tt.expectManifest) { - t.Errorf("expect manifest: %+v, got %+v", tt.expectManifest, manifest) - } - }) - } -} From fe79e22ac53b22257edb04de8f741f98d0aacff2 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Tue, 8 Nov 2022 18:12:44 +0800 Subject: [PATCH 04/10] update Signed-off-by: Patrick Zheng --- registry/repository.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/registry/repository.go b/registry/repository.go index b6b91b0a..cd13496f 100644 --- a/registry/repository.go +++ b/registry/repository.go @@ -43,9 +43,7 @@ func (c *repositoryClient) ListSignatures(ctx context.Context, desc ocispec.Desc return c.Repository.Referrers(ctx, ocispec.Descriptor{ Digest: desc.Digest, }, ArtifactTypeNotation, func(referrers []ocispec.Descriptor) error { - var sigManifestDesc []ocispec.Descriptor - sigManifestDesc = append(sigManifestDesc, referrers...) - return fn(sigManifestDesc) + return fn(referrers) }) } From 125ede9873810b160aaa03076fd473d8be653cfc Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Tue, 8 Nov 2022 18:13:33 +0800 Subject: [PATCH 05/10] update Signed-off-by: Patrick Zheng --- registry/repository.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/registry/repository.go b/registry/repository.go index cd13496f..7d9ce6d1 100644 --- a/registry/repository.go +++ b/registry/repository.go @@ -40,9 +40,7 @@ func (c *repositoryClient) Resolve(ctx context.Context, reference string) (ocisp // ListSignatures returns signature manifests filtered by fn given the // artifact manifest descriptor func (c *repositoryClient) ListSignatures(ctx context.Context, desc ocispec.Descriptor, fn func(signatureManifests []ocispec.Descriptor) error) error { - return c.Repository.Referrers(ctx, ocispec.Descriptor{ - Digest: desc.Digest, - }, ArtifactTypeNotation, func(referrers []ocispec.Descriptor) error { + return c.Repository.Referrers(ctx, desc, ArtifactTypeNotation, func(referrers []ocispec.Descriptor) error { return fn(referrers) }) } From c5a5fee0ff8700c00c52bd542ef9d23902197d95 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Wed, 9 Nov 2022 11:00:50 +0800 Subject: [PATCH 06/10] updated per code review Signed-off-by: Patrick Zheng --- registry/interface.go | 5 ++- registry/repository.go | 67 +++++++++++-------------------------- registry/repository_test.go | 7 ++-- 3 files changed, 27 insertions(+), 52 deletions(-) diff --git a/registry/interface.go b/registry/interface.go index 27de5965..5528e8d8 100644 --- a/registry/interface.go +++ b/registry/interface.go @@ -1,4 +1,4 @@ -// Package registry provides Repository for remote signing and verification +// Package registry provides access to signatures in a registry package registry import ( @@ -12,12 +12,15 @@ import ( type Repository interface { // Resolve resolves a reference(tag or digest) to a manifest descriptor Resolve(ctx context.Context, reference string) (ocispec.Descriptor, error) + // ListSignatures returns signature manifests filtered by fn given the // artifact manifest descriptor ListSignatures(ctx context.Context, desc ocispec.Descriptor, fn func(signatureManifests []ocispec.Descriptor) error) error + // FetchSignatureBlob returns signature envelope blob and descriptor given // signature manifest descriptor FetchSignatureBlob(ctx context.Context, desc ocispec.Descriptor) ([]byte, ocispec.Descriptor, error) + // PushSignature creates and uploads an signature manifest along with its // linked signature envelope blob. PushSignature(ctx context.Context, blob []byte, mediaType string, subject ocispec.Descriptor, annotations map[string]string) (blobDesc, manifestDesc ocispec.Descriptor, err error) diff --git a/registry/repository.go b/registry/repository.go index 7d9ce6d1..d7cf976a 100644 --- a/registry/repository.go +++ b/registry/repository.go @@ -1,18 +1,15 @@ package registry import ( - "bytes" "context" "encoding/json" "errors" "fmt" - "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" - artifactspec "github.com/oras-project/artifacts-spec/specs-go/v1" "oras.land/oras-go/v2" "oras.land/oras-go/v2/content" - "oras.land/oras-go/v2/registry/remote" + orasRegistry "oras.land/oras-go/v2/registry" ) const ( @@ -22,11 +19,11 @@ const ( // repositoryClient implements Repository type repositoryClient struct { - remote.Repository + orasRegistry.Repository } // NewRepository returns a new Repository -func NewRepository(repo remote.Repository) Repository { +func NewRepository(repo orasRegistry.Repository) Repository { return &repositoryClient{ Repository: repo, } @@ -40,9 +37,11 @@ func (c *repositoryClient) Resolve(ctx context.Context, reference string) (ocisp // ListSignatures returns signature manifests filtered by fn given the // artifact manifest descriptor func (c *repositoryClient) ListSignatures(ctx context.Context, desc ocispec.Descriptor, fn func(signatureManifests []ocispec.Descriptor) error) error { - return c.Repository.Referrers(ctx, desc, ArtifactTypeNotation, func(referrers []ocispec.Descriptor) error { - return fn(referrers) - }) + refFinder, ok := c.Repository.(orasRegistry.ReferrerFinder) + if !ok { + return errors.New("repo is not a orasRegistry.ReferrerFinder") + } + return refFinder.Referrers(ctx, desc, ArtifactTypeNotation, fn) } // FetchSignatureBlob returns signature envelope blob and descriptor given @@ -52,10 +51,10 @@ func (c *repositoryClient) FetchSignatureBlob(ctx context.Context, desc ocispec. if err != nil { return nil, ocispec.Descriptor{}, err } - if len(sigManifest.Blobs) == 0 { - return nil, ocispec.Descriptor{}, errors.New("signature manifest missing signature envelope blob") + if len(sigManifest.Blobs) != 1 { + return nil, ocispec.Descriptor{}, fmt.Errorf("signature manifest requries exactly one signature envelope blob, got %d", len(sigManifest.Blobs)) } - sigDesc := ociDescriptorFromArtifact(sigManifest.Blobs[0]) + sigDesc := sigManifest.Blobs[0] if sigDesc.Size > maxBlobSizeLimit { return nil, ocispec.Descriptor{}, fmt.Errorf("signature blob too large: %d", sigDesc.Size) } @@ -70,7 +69,7 @@ func (c *repositoryClient) FetchSignatureBlob(ctx context.Context, desc ocispec. // linked signature envelope blob. Upon successful, PushSignature returns // signature envelope blob and manifest descriptors. func (c *repositoryClient) PushSignature(ctx context.Context, blob []byte, mediaType string, subject ocispec.Descriptor, annotations map[string]string) (blobDesc, manifestDesc ocispec.Descriptor, err error) { - blobDesc, err = c.uploadSignature(ctx, blob, mediaType) + blobDesc, err = oras.PushBytes(ctx, c.Repository.Blobs(), mediaType, blob) if err != nil { return ocispec.Descriptor{}, ocispec.Descriptor{}, err } @@ -85,42 +84,28 @@ func (c *repositoryClient) PushSignature(ctx context.Context, blob []byte, media // getSignatureManifest returns signature manifest given signature manifest // descriptor -func (c *repositoryClient) getSignatureManifest(ctx context.Context, sigManifestDesc ocispec.Descriptor) (*artifactspec.Manifest, error) { - +func (c *repositoryClient) getSignatureManifest(ctx context.Context, sigManifestDesc ocispec.Descriptor) (*ocispec.Artifact, error) { repo := c.Repository - repo.ManifestMediaTypes = []string{ - artifactspec.MediaTypeArtifactManifest, + if sigManifestDesc.MediaType != ocispec.MediaTypeArtifactManifest { + return nil, fmt.Errorf("sigManifestDesc.MediaType requires %q, got %q", ocispec.MediaTypeArtifactManifest, sigManifestDesc.MediaType) } store := repo.Manifests() if sigManifestDesc.Size > maxManifestSizeLimit { - return &artifactspec.Manifest{}, fmt.Errorf("manifest too large: %d", sigManifestDesc.Size) + return nil, fmt.Errorf("manifest too large: %d", sigManifestDesc.Size) } manifestJSON, err := content.FetchAll(ctx, store, sigManifestDesc) if err != nil { - return &artifactspec.Manifest{}, err + return nil, err } - var sigManifest artifactspec.Manifest + var sigManifest ocispec.Artifact err = json.Unmarshal(manifestJSON, &sigManifest) if err != nil { - return &artifactspec.Manifest{}, err + return nil, err } return &sigManifest, nil } -// uploadSignature uploads the signature envelope blob to the registry -func (c *repositoryClient) uploadSignature(ctx context.Context, blob []byte, mediaType string) (ocispec.Descriptor, error) { - desc := ocispec.Descriptor{ - MediaType: mediaType, - Digest: digest.FromBytes(blob), - Size: int64(len(blob)), - } - if err := c.Repository.Blobs().Push(ctx, desc, bytes.NewReader(blob)); err != nil { - return ocispec.Descriptor{}, err - } - return desc, nil -} - // uploadSignatureManifest uploads the signature manifest to the registry func (c *repositoryClient) uploadSignatureManifest(ctx context.Context, subject, blobDesc ocispec.Descriptor, annotations map[string]string) (ocispec.Descriptor, error) { opts := oras.PackOptions{ @@ -128,17 +113,5 @@ func (c *repositoryClient) uploadSignatureManifest(ctx context.Context, subject, ManifestAnnotations: annotations, } - manifestDesc, err := oras.Pack(ctx, c.Repository.Manifests(), ArtifactTypeNotation, []ocispec.Descriptor{blobDesc}, opts) - if err != nil { - return ocispec.Descriptor{}, err - } - return manifestDesc, nil -} - -func ociDescriptorFromArtifact(desc artifactspec.Descriptor) ocispec.Descriptor { - return ocispec.Descriptor{ - MediaType: desc.MediaType, - Digest: desc.Digest, - Size: desc.Size, - } + return oras.Pack(ctx, c.Repository.Manifests(), ArtifactTypeNotation, []ocispec.Descriptor{blobDesc}, opts) } diff --git a/registry/repository_test.go b/registry/repository_test.go index 9ab60c64..71100909 100644 --- a/registry/repository_test.go +++ b/registry/repository_test.go @@ -13,7 +13,6 @@ import ( "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" - artifactspec "github.com/oras-project/artifacts-spec/specs-go/v1" "oras.land/oras-go/v2/registry" "oras.land/oras-go/v2/registry/remote" ) @@ -351,7 +350,7 @@ func TestFetchSignatureBlob(t *testing.T) { remoteClient: mockRemoteClient{}, plainHttp: false, signatureManifestDesc: ocispec.Descriptor{ - MediaType: artifactspec.MediaTypeArtifactManifest, + MediaType: ocispec.MediaTypeArtifactManifest, Digest: digest.Digest(invalidDigest), }, }, @@ -366,7 +365,7 @@ func TestFetchSignatureBlob(t *testing.T) { remoteClient: mockRemoteClient{}, plainHttp: false, signatureManifestDesc: ocispec.Descriptor{ - MediaType: artifactspec.MediaTypeArtifactManifest, + MediaType: ocispec.MediaTypeArtifactManifest, Digest: digest.Digest(validDigestWithAlgo3), }, }, @@ -476,7 +475,7 @@ func TestPushSignature(t *testing.T) { // newRepositoryClient creates a new repository client. func newRepositoryClient(client remote.Client, ref registry.Reference, plainHTTP bool) *repositoryClient { return &repositoryClient{ - Repository: remote.Repository{ + Repository: &remote.Repository{ Client: client, Reference: ref, PlainHTTP: plainHTTP, From 82b67aabc15e6a93a4afdc7c22ebb01c1bc0eabc Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Wed, 9 Nov 2022 12:34:50 +0800 Subject: [PATCH 07/10] updated per code review Signed-off-by: Patrick Zheng --- registry/repository.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/registry/repository.go b/registry/repository.go index d7cf976a..2357360d 100644 --- a/registry/repository.go +++ b/registry/repository.go @@ -9,7 +9,7 @@ import ( ocispec "github.com/opencontainers/image-spec/specs-go/v1" "oras.land/oras-go/v2" "oras.land/oras-go/v2/content" - orasRegistry "oras.land/oras-go/v2/registry" + "oras.land/oras-go/v2/registry" ) const ( @@ -19,11 +19,11 @@ const ( // repositoryClient implements Repository type repositoryClient struct { - orasRegistry.Repository + registry.Repository } // NewRepository returns a new Repository -func NewRepository(repo orasRegistry.Repository) Repository { +func NewRepository(repo registry.Repository) Repository { return &repositoryClient{ Repository: repo, } @@ -31,13 +31,14 @@ func NewRepository(repo orasRegistry.Repository) Repository { // Resolve resolves a reference(tag or digest) to a manifest descriptor func (c *repositoryClient) Resolve(ctx context.Context, reference string) (ocispec.Descriptor, error) { - return c.Repository.Resolve(ctx, reference) + return c.Repository.Manifests().Resolve(ctx, reference) } // ListSignatures returns signature manifests filtered by fn given the // artifact manifest descriptor func (c *repositoryClient) ListSignatures(ctx context.Context, desc ocispec.Descriptor, fn func(signatureManifests []ocispec.Descriptor) error) error { - refFinder, ok := c.Repository.(orasRegistry.ReferrerFinder) + // TODO: remove this part once oras v2.0.0-rc.5 is released + refFinder, ok := c.Repository.(registry.ReferrerFinder) if !ok { return errors.New("repo is not a orasRegistry.ReferrerFinder") } @@ -85,15 +86,13 @@ func (c *repositoryClient) PushSignature(ctx context.Context, blob []byte, media // getSignatureManifest returns signature manifest given signature manifest // descriptor func (c *repositoryClient) getSignatureManifest(ctx context.Context, sigManifestDesc ocispec.Descriptor) (*ocispec.Artifact, error) { - repo := c.Repository if sigManifestDesc.MediaType != ocispec.MediaTypeArtifactManifest { return nil, fmt.Errorf("sigManifestDesc.MediaType requires %q, got %q", ocispec.MediaTypeArtifactManifest, sigManifestDesc.MediaType) } - store := repo.Manifests() if sigManifestDesc.Size > maxManifestSizeLimit { return nil, fmt.Errorf("manifest too large: %d", sigManifestDesc.Size) } - manifestJSON, err := content.FetchAll(ctx, store, sigManifestDesc) + manifestJSON, err := content.FetchAll(ctx, c.Repository.Manifests(), sigManifestDesc) if err != nil { return nil, err } From c12adcdc81243600ebb994b08728642d814576f5 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Wed, 9 Nov 2022 15:26:45 +0800 Subject: [PATCH 08/10] updated per code review Signed-off-by: Patrick Zheng --- registry/interface.go | 8 ++++---- registry/repository.go | 9 ++++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/registry/interface.go b/registry/interface.go index 5528e8d8..9fb4452d 100644 --- a/registry/interface.go +++ b/registry/interface.go @@ -7,8 +7,8 @@ import ( ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) -// Repository provides registry functionalities for remote signing and -// verification. +// Repository provides registry functionalities for storage and retrieval +// of signature. type Repository interface { // Resolve resolves a reference(tag or digest) to a manifest descriptor Resolve(ctx context.Context, reference string) (ocispec.Descriptor, error) @@ -17,8 +17,8 @@ type Repository interface { // artifact manifest descriptor ListSignatures(ctx context.Context, desc ocispec.Descriptor, fn func(signatureManifests []ocispec.Descriptor) error) error - // FetchSignatureBlob returns signature envelope blob and descriptor given - // signature manifest descriptor + // FetchSignatureBlob returns signature envelope blob and descriptor for + // given signature manifest descriptor FetchSignatureBlob(ctx context.Context, desc ocispec.Descriptor) ([]byte, ocispec.Descriptor, error) // PushSignature creates and uploads an signature manifest along with its diff --git a/registry/repository.go b/registry/repository.go index 2357360d..efbd9637 100644 --- a/registry/repository.go +++ b/registry/repository.go @@ -40,7 +40,7 @@ func (c *repositoryClient) ListSignatures(ctx context.Context, desc ocispec.Desc // TODO: remove this part once oras v2.0.0-rc.5 is released refFinder, ok := c.Repository.(registry.ReferrerFinder) if !ok { - return errors.New("repo is not a orasRegistry.ReferrerFinder") + return errors.New("repo is not a registry.ReferrerFinder") } return refFinder.Referrers(ctx, desc, ArtifactTypeNotation, fn) } @@ -57,7 +57,7 @@ func (c *repositoryClient) FetchSignatureBlob(ctx context.Context, desc ocispec. } sigDesc := sigManifest.Blobs[0] if sigDesc.Size > maxBlobSizeLimit { - return nil, ocispec.Descriptor{}, fmt.Errorf("signature blob too large: %d", sigDesc.Size) + return nil, ocispec.Descriptor{}, fmt.Errorf("signature blob too large: %d bytes", sigDesc.Size) } sigBlob, err := content.FetchAll(ctx, c.Repository.Blobs(), sigDesc) if err != nil { @@ -79,6 +79,9 @@ func (c *repositoryClient) PushSignature(ctx context.Context, blob []byte, media if err != nil { return ocispec.Descriptor{}, ocispec.Descriptor{}, err } + if manifestDesc.MediaType != ocispec.MediaTypeArtifactManifest { + return ocispec.Descriptor{}, ocispec.Descriptor{}, fmt.Errorf("manifestDesc.MediaType requires %q, got %q", ocispec.MediaTypeArtifactManifest, manifestDesc.MediaType) + } return blobDesc, manifestDesc, nil } @@ -90,7 +93,7 @@ func (c *repositoryClient) getSignatureManifest(ctx context.Context, sigManifest return nil, fmt.Errorf("sigManifestDesc.MediaType requires %q, got %q", ocispec.MediaTypeArtifactManifest, sigManifestDesc.MediaType) } if sigManifestDesc.Size > maxManifestSizeLimit { - return nil, fmt.Errorf("manifest too large: %d", sigManifestDesc.Size) + return nil, fmt.Errorf("manifest too large: %d bytes", sigManifestDesc.Size) } manifestJSON, err := content.FetchAll(ctx, c.Repository.Manifests(), sigManifestDesc) if err != nil { From f1e97703a97ac29f46c0852db3d1e82b06d5e36c Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Wed, 9 Nov 2022 16:41:46 +0800 Subject: [PATCH 09/10] updated per code review Signed-off-by: Patrick Zheng --- registry/repository.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/registry/repository.go b/registry/repository.go index efbd9637..adb8dd6d 100644 --- a/registry/repository.go +++ b/registry/repository.go @@ -79,9 +79,6 @@ func (c *repositoryClient) PushSignature(ctx context.Context, blob []byte, media if err != nil { return ocispec.Descriptor{}, ocispec.Descriptor{}, err } - if manifestDesc.MediaType != ocispec.MediaTypeArtifactManifest { - return ocispec.Descriptor{}, ocispec.Descriptor{}, fmt.Errorf("manifestDesc.MediaType requires %q, got %q", ocispec.MediaTypeArtifactManifest, manifestDesc.MediaType) - } return blobDesc, manifestDesc, nil } From 541bcf4419548c5b9ec2c20a6c451ad0ef7b7607 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Wed, 9 Nov 2022 16:44:43 +0800 Subject: [PATCH 10/10] update Signed-off-by: Patrick Zheng --- registry/repository.go | 1 + 1 file changed, 1 insertion(+) diff --git a/registry/repository.go b/registry/repository.go index adb8dd6d..a153060b 100644 --- a/registry/repository.go +++ b/registry/repository.go @@ -38,6 +38,7 @@ func (c *repositoryClient) Resolve(ctx context.Context, reference string) (ocisp // artifact manifest descriptor func (c *repositoryClient) ListSignatures(ctx context.Context, desc ocispec.Descriptor, fn func(signatureManifests []ocispec.Descriptor) error) error { // TODO: remove this part once oras v2.0.0-rc.5 is released + // https://github.com/notaryproject/notation-go/issues/195 refFinder, ok := c.Repository.(registry.ReferrerFinder) if !ok { return errors.New("repo is not a registry.ReferrerFinder")