Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add local sign/list/verification for OCI layout directory #595

Merged
merged 61 commits into from
Apr 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
7370052
added local sign for oci layout folder
Two-Hearts Mar 1, 2023
fa008ca
added local verification for OCI layout folder
Two-Hearts Mar 2, 2023
9ea0f18
update
Two-Hearts Mar 3, 2023
4306629
resolved conflicts
Two-Hearts Mar 3, 2023
3412287
update
Two-Hearts Mar 3, 2023
c4c5a8e
added local sign for descriptor.json
Two-Hearts Mar 3, 2023
09651d3
Merge branch 'notaryproject:main' into local
Two-Hearts Mar 3, 2023
7493a51
update
Two-Hearts Mar 3, 2023
58e3864
update
Two-Hearts Mar 6, 2023
34d0889
Merge branch 'notaryproject:main' into local
Two-Hearts Mar 6, 2023
3612b4d
Merge branch 'local' of https://github.com/patrickzheng200/notation i…
Two-Hearts Mar 6, 2023
a90819f
Merge branch 'notaryproject:main' into local
Two-Hearts Mar 6, 2023
e9e0757
added verification for descriptor.json
Two-Hearts Mar 6, 2023
410617e
Merge branch 'local' of https://github.com/patrickzheng200/notation i…
Two-Hearts Mar 6, 2023
06e400a
update
Two-Hearts Mar 6, 2023
853eac1
Merge branch 'notaryproject:main' into local
Two-Hearts Mar 7, 2023
dfe8e91
update
Two-Hearts Mar 7, 2023
f16b4a6
Merge branch 'notaryproject:main' into local
Two-Hearts Mar 7, 2023
1f78788
added local sign/verification on OCI layout directory
Two-Hearts Mar 21, 2023
74cd415
Merge branch 'notaryproject:main' into local-rc4
Two-Hearts Mar 21, 2023
48180a5
update e2e test
Two-Hearts Mar 21, 2023
27abde2
refactored local sign/verification
Two-Hearts Mar 22, 2023
7f8e420
update
Two-Hearts Mar 23, 2023
62b7431
updated help
Two-Hearts Mar 23, 2023
4e9db0e
updated help
Two-Hearts Mar 23, 2023
4841fb8
updated log
Two-Hearts Mar 27, 2023
56faaed
update per code review
Two-Hearts Mar 28, 2023
1e6ed01
Merge branch 'notaryproject:main' into local-rc4
Two-Hearts Mar 29, 2023
ee6a242
updated per code review
Two-Hearts Mar 30, 2023
55577b9
Merge branch 'notaryproject:main' into local-rc4
Two-Hearts Mar 30, 2023
1ed276b
Merge branch 'local-rc4' of https://github.com/patrickzheng200/notati…
Two-Hearts Mar 30, 2023
01c3d1c
updated per code review
Two-Hearts Mar 30, 2023
8f78b50
Merge branch 'notaryproject:main' into local-rc4
Two-Hearts Mar 30, 2023
a511c06
Merge branch 'local-rc4' of https://github.com/patrickzheng200/notati…
Two-Hearts Mar 30, 2023
468d343
refactoring
Two-Hearts Mar 30, 2023
0aaf262
updated per code review
Two-Hearts Mar 31, 2023
f53958e
updated per code review
Two-Hearts Mar 31, 2023
85affcb
updated per code review
Two-Hearts Mar 31, 2023
b008e79
updated flag descriptions
Two-Hearts Mar 31, 2023
3b667ac
go mod tidy
Two-Hearts Apr 6, 2023
652c7ac
added [Experimental]
Two-Hearts Apr 10, 2023
da235f3
resolved conflicts
Two-Hearts Apr 13, 2023
9318819
added experimental checks
Two-Hearts Apr 13, 2023
1909117
fixed bug on experimental feature
Two-Hearts Apr 13, 2023
0b383a9
fixed e2e tests
Two-Hearts Apr 13, 2023
9c283aa
added e2e
Two-Hearts Apr 13, 2023
8d2272e
added tests
Two-Hearts Apr 13, 2023
42762ca
fix
Two-Hearts Apr 13, 2023
31aaa8b
fix
Two-Hearts Apr 13, 2023
70bea37
added verify tests
Two-Hearts Apr 13, 2023
3c4ac54
update
Two-Hearts Apr 13, 2023
1443c16
fix test
Two-Hearts Apr 13, 2023
2eb02b4
fixed bug in experimental
Two-Hearts Apr 14, 2023
9e3e70a
Merge branch 'notaryproject:main' into local-rc4
Two-Hearts Apr 17, 2023
8b18726
Merge branch 'notaryproject:main' into local-rc4
Two-Hearts Apr 17, 2023
1c4378b
updated based on spec
Two-Hearts Apr 17, 2023
54583e4
Merge branch 'notaryproject:main' into local-rc4
Two-Hearts Apr 17, 2023
9332d0f
updated dependency as notation-go PR has been merged
Two-Hearts Apr 19, 2023
f6d1c1d
updated per code review
Two-Hearts Apr 19, 2023
e39882d
fix
Two-Hearts Apr 19, 2023
529a6df
updated warning msg
Two-Hearts Apr 20, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 6 additions & 12 deletions cmd/notation/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import (
"errors"
"fmt"
"os"
"strings"
"strconv"
"strings"
"time"

"github.com/notaryproject/notation-core-go/signature"
Expand Down Expand Up @@ -98,22 +98,16 @@ func runInspect(command *cobra.Command, opts *inspectOpts) error {

// initialize
reference := opts.reference
sigRepo, err := getSignatureRepository(ctx, &opts.SecureFlagOpts, reference)
sigRepo, err := getRemoteRepository(ctx, &opts.SecureFlagOpts, reference)
if err != nil {
return err
}

manifestDesc, ref, err := getManifestDescriptor(ctx, &opts.SecureFlagOpts, reference, sigRepo)
manifestDesc, resolvedRef, err := resolveReference(ctx, inputTypeRegistry, reference, sigRepo, func(ref string, manifestDesc ocispec.Descriptor) {
fmt.Fprintf(os.Stderr, "Warning: Always inspect the artifact using digest(@sha256:...) rather than a tag(:%s) because resolved digest may not point to the same signed artifact, as tags are mutable.\n", ref)
})
if err != nil {
return err
}

// reference is a digest reference
if err := ref.ValidateReferenceAsDigest(); err != nil {
fmt.Fprintf(os.Stderr, "Warning: Always inspect the artifact using digest(@sha256:...) rather than a tag(:%s) because resolved digest may not point to the same signed artifact, as tags are mutable.\n", ref.Reference)
ref.Reference = manifestDesc.Digest.String()
}

output := inspectOutput{MediaType: manifestDesc.MediaType, Signatures: []signatureOutput{}}
skippedSignatures := false
err = sigRepo.ListSignatures(ctx, manifestDesc, func(signatureManifests []ocispec.Descriptor) error {
Expand Down Expand Up @@ -177,7 +171,7 @@ func runInspect(command *cobra.Command, opts *inspectOpts) error {
return err
}

err = printOutput(opts.outputFormat, ref.String(), output)
err = printOutput(opts.outputFormat, resolvedRef, output)
if err != nil {
return err
}
Expand Down
13 changes: 13 additions & 0 deletions cmd/notation/internal/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,16 @@ func (e ErrorReferrersAPINotSupported) Error() string {
}
return "referrers API not supported"
}

// ErrorOCILayoutMissingReference is used when signing local content in oci
// layout folder but missing input tag or digest.
type ErrorOCILayoutMissingReference struct {
Msg string
}

func (e ErrorOCILayoutMissingReference) Error() string {
if e.Msg != "" {
return e.Msg
}
return "reference is missing either digest or tag"
}
28 changes: 19 additions & 9 deletions cmd/notation/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,25 @@ import (

notationregistry "github.com/notaryproject/notation-go/registry"
"github.com/notaryproject/notation/internal/cmd"
"github.com/notaryproject/notation/internal/experimental"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/spf13/cobra"
"oras.land/oras-go/v2/registry"
)

type listOpts struct {
cmd.LoggingFlagOpts
SecureFlagOpts
reference string
ociLayout bool
inputType inputType
}

func listCommand(opts *listOpts) *cobra.Command {
if opts == nil {
opts = &listOpts{}
opts = &listOpts{
inputType: inputTypeRegistry, // remote registry by default
}
}
cmd := &cobra.Command{
Use: "list [flags] <reference>",
Expand All @@ -35,12 +39,20 @@ func listCommand(opts *listOpts) *cobra.Command {
opts.reference = args[0]
return nil
},
PreRunE: func(cmd *cobra.Command, args []string) error {
if opts.ociLayout {
opts.inputType = inputTypeOCILayout
}
return experimental.CheckFlagsAndWarn(cmd, "oci-layout")
},
RunE: func(cmd *cobra.Command, args []string) error {
return runList(cmd.Context(), opts)
},
}
opts.LoggingFlagOpts.ApplyFlags(cmd.Flags())
opts.SecureFlagOpts.ApplyFlags(cmd.Flags())
cmd.Flags().BoolVar(&opts.ociLayout, "oci-layout", false, "[Experimental] list signatures stored in OCI image layout")
experimental.HideFlags(cmd, "oci-layout")
return cmd
}

Expand All @@ -50,23 +62,21 @@ func runList(ctx context.Context, opts *listOpts) error {

// initialize
reference := opts.reference
sigRepo, err := getSignatureRepository(ctx, &opts.SecureFlagOpts, reference)
sigRepo, err := getRepository(ctx, opts.inputType, reference, &opts.SecureFlagOpts)
if err != nil {
return err
}
manifestDesc, ref, err := getManifestDescriptor(ctx, &opts.SecureFlagOpts, reference, sigRepo)
targetDesc, resolvedRef, err := resolveReference(ctx, opts.inputType, reference, sigRepo, nil)
if err != nil {
return err
}

// print all signature manifest digests
return printSignatureManifestDigests(ctx, manifestDesc, sigRepo, ref)
return printSignatureManifestDigests(ctx, targetDesc, sigRepo, resolvedRef)
}

// printSignatureManifestDigests returns the signature manifest digests of
// the subject manifest.
func printSignatureManifestDigests(ctx context.Context, manifestDesc ocispec.Descriptor, sigRepo notationregistry.Repository, ref registry.Reference) error {
ref.Reference = manifestDesc.Digest.String()
func printSignatureManifestDigests(ctx context.Context, targetDesc ocispec.Descriptor, sigRepo notationregistry.Repository, ref string) error {
titlePrinted := false
printTitle := func() {
if !titlePrinted {
Expand All @@ -77,7 +87,7 @@ func printSignatureManifestDigests(ctx context.Context, manifestDesc ocispec.Des
}

var prevDigest digest.Digest
err := sigRepo.ListSignatures(ctx, manifestDesc, func(signatureManifests []ocispec.Descriptor) error {
err := sigRepo.ListSignatures(ctx, targetDesc, func(signatureManifests []ocispec.Descriptor) error {
for _, sigManifestDesc := range signatureManifests {
if prevDigest != "" {
// check and print title
Expand Down
115 changes: 100 additions & 15 deletions cmd/notation/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,119 @@ package main
import (
"context"
"errors"
"fmt"
"os"
"strings"
"unicode"

"github.com/notaryproject/notation-go/log"
notationregistry "github.com/notaryproject/notation-go/registry"
notationerrors "github.com/notaryproject/notation/cmd/notation/internal/errors"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"oras.land/oras-go/v2/registry"
)

// getManifestDescriptor returns target artifact manifest descriptor and
// registry.Reference given user input reference.
func getManifestDescriptor(ctx context.Context, opts *SecureFlagOpts, reference string, sigRepo notationregistry.Repository) (ocispec.Descriptor, registry.Reference, error) {
logger := log.GetLogger(ctx)

// resolveReference resolves user input reference based on user input type.
// Returns the resolved manifest descriptor and resolvedRef in digest
func resolveReference(ctx context.Context, inputType inputType, reference string, sigRepo notationregistry.Repository, fn func(string, ocispec.Descriptor)) (ocispec.Descriptor, string, error) {
// sanity check
if reference == "" {
return ocispec.Descriptor{}, registry.Reference{}, errors.New("missing reference")
return ocispec.Descriptor{}, "", errors.New("missing user input reference")
}
var tagOrDigestRef string
var resolvedRef string
switch inputType {
case inputTypeRegistry:
ref, err := registry.ParseReference(reference)
if err != nil {
return ocispec.Descriptor{}, "", fmt.Errorf("failed to resolve user input reference: %w", err)
}
tagOrDigestRef = ref.Reference
resolvedRef = ref.Registry + "/" + ref.Repository
case inputTypeOCILayout:
layoutPath, layoutReference, err := parseOCILayoutReference(reference)
if err != nil {
return ocispec.Descriptor{}, "", fmt.Errorf("failed to resolve user input reference: %w", err)
}
layoutPathInfo, err := os.Stat(layoutPath)
if err != nil {
return ocispec.Descriptor{}, "", fmt.Errorf("failed to resolve user input reference: %w", err)
}
if !layoutPathInfo.IsDir() {
return ocispec.Descriptor{}, "", errors.New("failed to resolve user input reference: input path is not a dir")
}
tagOrDigestRef = layoutReference
resolvedRef = layoutPath
default:
return ocispec.Descriptor{}, "", fmt.Errorf("unsupported user inputType: %d", inputType)
}
ref, err := registry.ParseReference(reference)

manifestDesc, err := getManifestDescriptor(ctx, tagOrDigestRef, sigRepo)
if err != nil {
return ocispec.Descriptor{}, registry.Reference{}, err
return ocispec.Descriptor{}, "", fmt.Errorf("failed to get manifest descriptor: %w", err)
}
resolvedRef = resolvedRef + "@" + manifestDesc.Digest.String()
if _, err := digest.Parse(tagOrDigestRef); err == nil {
// tagOrDigestRef is a digest reference
return manifestDesc, resolvedRef, nil
}
if ref.Reference == "" {
return ocispec.Descriptor{}, registry.Reference{}, errors.New("reference is missing digest or tag")
// tagOrDigestRef is a tag reference
if fn != nil {
fn(tagOrDigestRef, manifestDesc)
}
return manifestDesc, resolvedRef, nil
}

manifestDesc, err := sigRepo.Resolve(ctx, ref.Reference)
if err != nil {
return ocispec.Descriptor{}, registry.Reference{}, err
// resolveArtifactDigestReference creates reference in Verification given user input
// trust policy scope
func resolveArtifactDigestReference(reference, policyScope string) string {
if policyScope != "" {
if _, digest, ok := strings.Cut(reference, "@"); ok {
return policyScope + "@" + digest
}
}
return reference
}

logger.Infof("Reference %s resolved to manifest descriptor: %+v", ref.Reference, manifestDesc)
return manifestDesc, ref, nil
// parseOCILayoutReference parses the raw in format of <path>[:<tag>|@<digest>].
// Returns the path to the OCI layout and the reference (tag or digest).
func parseOCILayoutReference(raw string) (string, string, error) {
var path string
var ref string
if idx := strings.LastIndex(raw, "@"); idx != -1 {
// `digest` found
path, ref = raw[:idx], raw[idx+1:]
} else {
// find `tag`
idx := strings.LastIndex(raw, ":")
if idx == -1 || (idx == 1 && len(raw) > 2 && unicode.IsLetter(rune(raw[0])) && raw[2] == '\\') {
return "", "", notationerrors.ErrorOCILayoutMissingReference{}
} else {
path, ref = raw[:idx], raw[idx+1:]
}
}
if path == "" {
return "", "", fmt.Errorf("found empty file path in %q", raw)
}
if ref == "" {
return "", "", fmt.Errorf("found empty reference in %q", raw)
}
return path, ref, nil
}

// getManifestDescriptor returns target artifact manifest descriptor given
// reference (digest or tag) and Repository.
func getManifestDescriptor(ctx context.Context, reference string, sigRepo notationregistry.Repository) (ocispec.Descriptor, error) {
logger := log.GetLogger(ctx)

if reference == "" {
return ocispec.Descriptor{}, errors.New("reference cannot be empty")
}
manifestDesc, err := sigRepo.Resolve(ctx, reference)
if err != nil {
return ocispec.Descriptor{}, err
}
logger.Infof("Reference %s resolved to manifest descriptor: %+v", reference, manifestDesc)
return manifestDesc, nil
}
52 changes: 48 additions & 4 deletions cmd/notation/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,53 @@ import (
"oras.land/oras-go/v2/registry/remote/errcode"
)

const zeroDigest = "sha256:0000000000000000000000000000000000000000000000000000000000000000"
// inputType denotes the user input type
type inputType int

func getSignatureRepository(ctx context.Context, opts *SecureFlagOpts, reference string) (notationregistry.Repository, error) {
const (
inputTypeRegistry inputType = 1 + iota // inputType remote registry
inputTypeOCILayout // inputType oci-layout
)

const (
zeroDigest = "sha256:0000000000000000000000000000000000000000000000000000000000000000"
)

// getRepository returns a notationregistry.Repository given user input type and
// user input reference
func getRepository(ctx context.Context, inputType inputType, reference string, opts *SecureFlagOpts) (notationregistry.Repository, error) {
switch inputType {
case inputTypeRegistry:
return getRemoteRepository(ctx, opts, reference)
case inputTypeOCILayout:
layoutPath, _, err := parseOCILayoutReference(reference)
if err != nil {
return nil, err
}
return notationregistry.NewOCIRepository(layoutPath, notationregistry.RepositoryOptions{})
default:
return nil, errors.New("unsupported input type")
}
}

// getRepositoryForSign returns a notationregistry.Repository given user input
// type and user input reference during Sign process
func getRepositoryForSign(ctx context.Context, inputType inputType, reference string, opts *SecureFlagOpts, ociImageManifest bool) (notationregistry.Repository, error) {
switch inputType {
case inputTypeRegistry:
return getRemoteRepositoryForSign(ctx, opts, reference, ociImageManifest)
case inputTypeOCILayout:
layoutPath, _, err := parseOCILayoutReference(reference)
if err != nil {
return nil, err
}
return notationregistry.NewOCIRepository(layoutPath, notationregistry.RepositoryOptions{OCIImageManifest: ociImageManifest})
default:
return nil, errors.New("unsupported input type")
}
}

func getRemoteRepository(ctx context.Context, opts *SecureFlagOpts, reference string) (notationregistry.Repository, error) {
ref, err := registry.ParseReference(reference)
if err != nil {
return nil, err
Expand All @@ -37,13 +81,13 @@ func getSignatureRepository(ctx context.Context, opts *SecureFlagOpts, reference
return notationregistry.NewRepository(remoteRepo), nil
}

// getSignatureRepositoryForSign returns a registry.Repository for Sign.
// getRemoteRepositoryForSign returns a registry.Repository for Sign.
// ociImageManifest denotes the type of manifest used to store signatures during
// Sign process.
// Setting ociImageManifest to true means using OCI image manifest and the
// Referrers tag schema.
// Otherwise, use OCI artifact manifest and requires the Referrers API.
func getSignatureRepositoryForSign(ctx context.Context, opts *SecureFlagOpts, reference string, ociImageManifest bool) (notationregistry.Repository, error) {
func getRemoteRepositoryForSign(ctx context.Context, opts *SecureFlagOpts, reference string, ociImageManifest bool) (notationregistry.Repository, error) {
logger := log.GetLogger(ctx)
ref, err := registry.ParseReference(reference)
if err != nil {
Expand Down
Loading