diff --git a/cmd/notation/blob.go b/cmd/notation/blob.go deleted file mode 100644 index 65e36a530..000000000 --- a/cmd/notation/blob.go +++ /dev/null @@ -1,232 +0,0 @@ -// Copyright The Notary Project Authors. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -//Verify this -import ( - "context" - "errors" - "fmt" - "github.com/notaryproject/notation-core-go/signature" - "github.com/notaryproject/notation-go" - "github.com/notaryproject/notation-go/plugin/proto" - "github.com/notaryproject/notation-go/registry" - "github.com/notaryproject/notation/internal/cmd" - "github.com/notaryproject/notation/internal/envelope" - ocispec "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/spf13/cobra" - "os" - "strings" - "time" -) - -type blobOpts struct { - cmd.LoggingFlagOpts - cmd.SignerFlagOpts - SecureFlagOpts - expiry time.Duration - desc ocispec.Descriptor - sigRepo registry.Repository - pluginConfig []string - userMetadata []string - blobPath string - signaturePath string - outputFormat string -} - -func blobSignCommand(opts *blobOpts) *cobra.Command { - if opts == nil { - opts = &blobOpts{} - } - longMessage := `Sign BLOB artifacts - -Note: a signing key must be specified. This can be done temporarily by specifying a key ID, or a new key can be configured using the command "notation key add" - -Example - Sign a BLOB artifact using the default signing key, with the default JWS envelope, and use BLOB image manifest to store the signature: - notation blob sign - -Example - Sign a BLOB artifact by generating the signature in a particular directory: - notation blob sign --signature-directory - -Example - Sign a BLOB artifact and skip user confirmations when overwriting existing signature: - notation blob sign --force - -Example - Sign a BLOB artifact using the default signing key, with the COSE envelope: - notation blob sign --signature-format cose - -Example - Sign a BLOB artifact with a specified plugin and signing key stored in KMS: - notation blob sign --plugin --id - -Example - Sign a BLOB artifact and add a user metadata to payload: - notation blob sign --user-metadata - -Example - Sign a BLOB artifact using a specified media type: - notation blob sign --media-type - -Example - Sign a BLOB artifact using a specified key: - notation blob sign --key - -Example - Sign a BLOB artifact and specify the signature expiry duration, for example 24 hours: - notation blob sign --expiry 24h -` - - command := &cobra.Command{ - Use: "blob sign [flags] ", - Short: "Sign BLOB artifacts", - Long: longMessage, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) == 0 { - return errors.New("missing blob_path") - } - opts.blobPath = args[0] - return nil - }, - RunE: func(cmd *cobra.Command, args []string) error { - return runBlobSign(cmd, opts) - }, - } - opts.LoggingFlagOpts.ApplyFlags(command.Flags()) - opts.SignerFlagOpts.ApplyFlagsToCommand(command) - opts.SecureFlagOpts.ApplyFlags(command.Flags()) - cmd.SetPflagExpiry(command.Flags(), &opts.expiry) - cmd.SetPflagPluginConfig(command.Flags(), &opts.pluginConfig) - cmd.SetPflagUserMetadata(command.Flags(), &opts.userMetadata, cmd.PflagUserMetadataSignUsage) - //PlaceHolder for MediaType and Signature-directory - return command -} - -func runBlobSign(command *cobra.Command, cmdOpts *blobOpts) error { - // set log level - ctx := cmdOpts.LoggingFlagOpts.InitializeLogger(command.Context()) - - // initialize - signer, err := cmd.GetSigner(ctx, &cmdOpts.SignerFlagOpts) - if err != nil { - return err - } - blobOpts, err := prepareBlobSigningOpts(ctx, cmdOpts) - if err != nil { - return err - } - - // core process - err = notation.BlobSign(ctx, signer, blobOpts) //PlaceHolder - if err != nil { - var errorPushSignatureFailed notation.ErrorPushSignatureFailed - if errors.As(err, &errorPushSignatureFailed) && strings.Contains(err.Error(), referrersTagSchemaDeleteError) { - fmt.Fprintln(os.Stderr, "Warning: Removal of outdated referrers index from remote registry failed. Garbage collection may be required.") - // write out - fmt.Println("Successfully signed") - return nil - } - return err - } - fmt.Println("Successfully signed") - return nil -} - -func prepareBlobSigningOpts(ctx context.Context, opts *blobOpts) (notation.SignOptions, error) { - mediaType, err := envelope.GetEnvelopeMediaType(opts.SignerFlagOpts.SignatureFormat) - if err != nil { - return notation.SignOptions{}, err - } - pluginConfig, err := cmd.ParseFlagMap(opts.pluginConfig, cmd.PflagPluginConfig.Name) - if err != nil { - return notation.SignOptions{}, err - } - userMetadata, err := cmd.ParseFlagMap(opts.userMetadata, cmd.PflagUserMetadata.Name) - if err != nil { - return notation.SignOptions{}, err - } - blobOpts := notation.SignOptions{ - SignerSignOptions: notation.SignerSignOptions{ - SignatureMediaType: mediaType, - ExpiryDuration: opts.expiry, - PluginConfig: pluginConfig, - }, - UserMetadata: userMetadata, - } - return blobOpts, nil -} - -func blobInspectCommand(opts *blobOpts) *cobra.Command { - if opts == nil { - opts = &blobOpts{} - } - longMessage := `Inspect all signatures associated with the signed artifact. - -Example - Inspect signatures on an BLOB artifact: - notation blob inspect - -Example - Inspect signatures on an BLOB artifact output as json: - notation blob inspect --output json -` - - command := &cobra.Command{ - Use: "blob inspect [signaturePath]", - Short: "Inspect all signatures associated with the signed artifact", - Long: longMessage, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) == 0 { - return errors.New("missing signature_path") - } - opts.signaturePath = args[0] - return nil - }, - RunE: func(cmd *cobra.Command, args []string) error { - return runBlobInspect(cmd, opts) - }, - } - - opts.LoggingFlagOpts.ApplyFlags(command.Flags()) - opts.SecureFlagOpts.ApplyFlags(command.Flags()) - cmd.SetPflagOutput(command.Flags(), &opts.outputFormat, cmd.PflagOutputUsage) - return command -} - -func runBlobInspect(command *cobra.Command, opts *blobOpts) error { - // set log level - ctx := opts.LoggingFlagOpts.InitializeLogger(command.Context()) - - if opts.outputFormat != cmd.OutputJSON && opts.outputFormat != cmd.OutputPlaintext { - return fmt.Errorf("unrecognized output format %s", opts.outputFormat) - } - - output := inspectOutput{MediaType: opts.desc.MediaType, Signatures: []signatureOutput{}} - - sigBlob, _, _ := opts.sigRepo.FetchSignatureBlob(ctx, opts.desc) - sigEnvelope, _ := signature.ParseEnvelope(opts.desc.MediaType, sigBlob) - envelopeContent, _ := sigEnvelope.Content() - signedArtifactDesc, _ := envelope.DescriptorFromSignaturePayload(&envelopeContent.Payload) - signatureAlgorithm, _ := proto.EncodeSigningAlgorithm(envelopeContent.SignerInfo.SignatureAlgorithm) - - sig := signatureOutput{ - MediaType: opts.desc.MediaType, - Digest: opts.desc.Digest.String(), - SignatureAlgorithm: string(signatureAlgorithm), - SignedAttributes: getSignedAttributes(opts.outputFormat, envelopeContent), - UserDefinedAttributes: signedArtifactDesc.Annotations, - UnsignedAttributes: getUnsignedAttributes(envelopeContent), - Certificates: getCertificates(opts.outputFormat, envelopeContent), - SignedArtifact: *signedArtifactDesc, - } - - // clearing annotations from the SignedArtifact field since they're already - // displayed as UserDefinedAttributes - sig.SignedArtifact.Annotations = nil - - output.Signatures = append(output.Signatures, sig) - - return nil -} diff --git a/cmd/notation/blob/cmd.go b/cmd/notation/blob/cmd.go new file mode 100644 index 000000000..728cb7ccd --- /dev/null +++ b/cmd/notation/blob/cmd.go @@ -0,0 +1,30 @@ +// Copyright The Notary Project Authors. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package blob + +import "github.com/spf13/cobra" + +func Cmd() *cobra.Command { + command := &cobra.Command{ + Use: "blob [command]", + Short: "Commands for BLOB artifacts", + } + + command.AddCommand( + inspectCommand(nil), + signCommand(nil), + ) + + return command +} diff --git a/cmd/notation/blob/inspect.go b/cmd/notation/blob/inspect.go new file mode 100644 index 000000000..0286215dc --- /dev/null +++ b/cmd/notation/blob/inspect.go @@ -0,0 +1,157 @@ +// Copyright The Notary Project Authors. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package blob + +import ( + "errors" + "fmt" + "github.com/notaryproject/notation-core-go/signature" + "github.com/notaryproject/notation-go/plugin/proto" + "github.com/notaryproject/notation-go/registry" + "github.com/notaryproject/notation/cmd/notation/internal/sharedutils" + "github.com/notaryproject/notation/internal/cmd" + "github.com/notaryproject/notation/internal/envelope" + "github.com/notaryproject/notation/internal/ioutil" + "github.com/notaryproject/notation/internal/tree" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/spf13/cobra" + "strconv" +) + +type blobInspectOpts struct { + cmd.LoggingFlagOpts + cmd.SignerFlagOpts + desc ocispec.Descriptor + sigRepo registry.Repository + signaturePath string + outputFormat string + MediaType string +} + +func inspectCommand(opts *blobInspectOpts) *cobra.Command { + if opts == nil { + opts = &blobInspectOpts{} + } + longMessage := `Inspect all signatures associated with the signed artifact. + +Example - Inspect signatures on an BLOB artifact: + notation blob inspect + +Example - Inspect signatures on an BLOB artifact output as json: + notation blob inspect --output json +` + + command := &cobra.Command{ + Use: "blob inspect [signaturePath]", + Short: "Inspect all signatures associated with the signed artifact", + Long: longMessage, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errors.New("missing signature path to the artifact: use `notation blob inspect --help` to see what parameters are required") + } + opts.signaturePath = args[0] + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + return runBlobInspect(cmd, opts) + }, + } + + opts.LoggingFlagOpts.ApplyFlags(command.Flags()) + cmd.SetPflagOutput(command.Flags(), &opts.outputFormat, cmd.PflagOutputUsage) + return command +} + +func runBlobInspect(command *cobra.Command, opts *blobInspectOpts) error { + // set log level + ctx := opts.LoggingFlagOpts.InitializeLogger(command.Context()) + + if opts.outputFormat != cmd.OutputJSON && opts.outputFormat != cmd.OutputPlaintext { + return fmt.Errorf("unrecognized output format %s", opts.outputFormat) + } + + output := sharedutils.InspectOutput{MediaType: opts.MediaType, Signatures: []sharedutils.SignatureOutput{}} + //Added sigRepo and desc as placeholders. Once notation-go changes are merged revisit and update FetchSignatureBlob + sigBlob, _, _ := opts.sigRepo.FetchSignatureBlob(ctx, opts.desc) + sigEnvelope, _ := signature.ParseEnvelope(opts.MediaType, sigBlob) + envelopeContent, _ := sigEnvelope.Content() + signedArtifactDesc, _ := envelope.DescriptorFromSignaturePayload(&envelopeContent.Payload) + signatureAlgorithm, _ := proto.EncodeSigningAlgorithm(envelopeContent.SignerInfo.SignatureAlgorithm) + + sig := sharedutils.SignatureOutput{ + SignatureAlgorithm: string(signatureAlgorithm), + SignedAttributes: sharedutils.GetSignedAttributes(opts.outputFormat, envelopeContent), + UserDefinedAttributes: signedArtifactDesc.Annotations, + UnsignedAttributes: sharedutils.GetUnsignedAttributes(envelopeContent), + Certificates: sharedutils.GetCertificates(opts.outputFormat, envelopeContent), + SignedArtifact: *signedArtifactDesc, + } + + // clearing annotations from the SignedArtifact field since they're already + // displayed as UserDefinedAttributes + sig.SignedArtifact.Annotations = nil + + output.Signatures = append(output.Signatures, sig) + blobPath := "placeholder" + if err := printOutput(opts.outputFormat, blobPath, output); err != nil { + return err + } + return nil +} + +func printOutput(outputFormat string, blobPath string, output sharedutils.InspectOutput) error { + if outputFormat == cmd.OutputJSON { + return ioutil.PrintObjectAsJSON(output) + } + + if len(output.Signatures) == 0 { + fmt.Printf("%s has no associated signature\n", blobPath) + return nil + } + + fmt.Println("Inspecting all signatures for signed artifact") + root := tree.New(blobPath) + cncfSigNode := root.Add(registry.ArtifactTypeNotation) + + for _, signature := range output.Signatures { + sigNode := cncfSigNode.Add(signature.Digest) + sigNode.AddPair("media type", signature.MediaType) + sigNode.AddPair("signature algorithm", signature.SignatureAlgorithm) + + signedAttributesNode := sigNode.Add("signed attributes") + sharedutils.AddMapToTree(signedAttributesNode, signature.SignedAttributes) + + userDefinedAttributesNode := sigNode.Add("user defined attributes") + sharedutils.AddMapToTree(userDefinedAttributesNode, signature.UserDefinedAttributes) + + unsignedAttributesNode := sigNode.Add("unsigned attributes") + sharedutils.AddMapToTree(unsignedAttributesNode, signature.UnsignedAttributes) + + certListNode := sigNode.Add("certificates") + for _, cert := range signature.Certificates { + certNode := certListNode.AddPair("SHA256 fingerprint", cert.SHA256Fingerprint) + certNode.AddPair("issued to", cert.IssuedTo) + certNode.AddPair("issued by", cert.IssuedBy) + certNode.AddPair("expiry", cert.Expiry) + } + + artifactNode := sigNode.Add("signed artifact") + artifactNode.AddPair("media type", signature.SignedArtifact.MediaType) + artifactNode.AddPair("digest", signature.SignedArtifact.Digest.String()) + artifactNode.AddPair("size", strconv.FormatInt(signature.SignedArtifact.Size, 10)) + } + + root.Print() + return nil +} diff --git a/cmd/notation/blob/inspect_test.go b/cmd/notation/blob/inspect_test.go new file mode 100644 index 000000000..a51b5786d --- /dev/null +++ b/cmd/notation/blob/inspect_test.go @@ -0,0 +1,56 @@ +package blob + +import ( + "github.com/notaryproject/notation/internal/cmd" + "reflect" + "testing" +) + +func TestBlobInspectCommand_SecretsFromArgs(t *testing.T) { + opts := &blobInspectOpts{} + command := inspectCommand(opts) + expected := &blobInspectOpts{ + signaturePath: "path", + outputFormat: cmd.OutputPlaintext} + if err := command.ParseFlags([]string{ + expected.signaturePath, + "--output", "text"}); err != nil { + t.Fatalf("Parse Flag failed: %v", err) + } + if err := command.Args(command, command.Flags().Args()); err != nil { + t.Fatalf("Parse Args failed: %v", err) + } + if !reflect.DeepEqual(*expected, *opts) { + t.Fatalf("Expect blob inspect opts: %v, got: %v", expected, opts) + } +} + +func TestBlobInspectCommand_SecretsFromEnv(t *testing.T) { + opts := &blobInspectOpts{} + expected := &blobInspectOpts{ + signaturePath: "path", + outputFormat: cmd.OutputJSON, + } + command := inspectCommand(opts) + if err := command.ParseFlags([]string{ + expected.signaturePath, + "--output", "json"}); err != nil { + t.Fatalf("Parse Flag failed: %v", err) + } + if err := command.Args(command, command.Flags().Args()); err != nil { + t.Fatalf("Parse Args failed: %v", err) + } + if !reflect.DeepEqual(*expected, *opts) { + t.Fatalf("Expect blob inspect opts: %v, got: %v", expected, opts) + } +} + +func TestBlobInspectCommand_MissingArgs(t *testing.T) { + command := inspectCommand(nil) + if err := command.ParseFlags(nil); err != nil { + t.Fatalf("Parse Flag failed: %v", err) + } + if err := command.Args(command, command.Flags().Args()); err == nil { + t.Fatal("Parse Args expected error, but ok") + } +} diff --git a/cmd/notation/blob_test.go b/cmd/notation/blob_test.go deleted file mode 100644 index caa8c78f5..000000000 --- a/cmd/notation/blob_test.go +++ /dev/null @@ -1,408 +0,0 @@ -// Copyright The Notary Project Authors. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -import ( - "fmt" - "reflect" - "testing" - "time" - - "github.com/notaryproject/notation/internal/cmd" - "github.com/notaryproject/notation/internal/envelope" -) - -func TestBlobSignCommand_BasicArgs(t *testing.T) { - opts := &blobOpts{} - command := blobSignCommand(opts) - expected := &blobOpts{ - blobPath: "path", - SecureFlagOpts: SecureFlagOpts{ - Username: "user", - Password: "password", - }, - SignerFlagOpts: cmd.SignerFlagOpts{ - Key: "key", - SignatureFormat: envelope.JWS, - }, - } - if err := command.ParseFlags([]string{ - expected.blobPath, - "-u", expected.Username, - "--password", expected.Password, - "--key", expected.Key}); err != nil { - t.Fatalf("Parse Flag failed: %v", err) - } - if err := command.Args(command, command.Flags().Args()); err != nil { - t.Fatalf("Parse args failed: %v", err) - } - if !reflect.DeepEqual(*expected, *opts) { - t.Fatalf("Expect blob sign opts: %v, got: %v", expected, opts) - } -} - -func TestBlobSignCommand_MoreArgs(t *testing.T) { - opts := &blobOpts{} - command := blobSignCommand(opts) - expected := &blobOpts{ - blobPath: "path", - SecureFlagOpts: SecureFlagOpts{ - Username: "user", - Password: "password", - InsecureRegistry: true, - }, - SignerFlagOpts: cmd.SignerFlagOpts{ - Key: "key", - SignatureFormat: envelope.COSE, - }, - expiry: 24 * time.Hour, - } - if err := command.ParseFlags([]string{ - expected.blobPath, - "-u", expected.Username, - "-p", expected.Password, - "--key", expected.Key, - "--insecure-registry", - "--signature-format", expected.SignerFlagOpts.SignatureFormat, - "--expiry", expected.expiry.String()}); err != nil { - t.Fatalf("Parse Flag failed: %v", err) - } - if err := command.Args(command, command.Flags().Args()); err != nil { - t.Fatalf("Parse args failed: %v", err) - } - if !reflect.DeepEqual(*expected, *opts) { - t.Fatalf("Expect blob sign opts: %v, got: %v", expected, opts) - } -} - -func TestBlobSignCommand_CorrectConfig(t *testing.T) { - opts := &blobOpts{} - command := blobSignCommand(opts) - expected := &blobOpts{ - blobPath: "path", - SignerFlagOpts: cmd.SignerFlagOpts{ - Key: "key", - SignatureFormat: envelope.COSE, - }, - expiry: 365 * 24 * time.Hour, - pluginConfig: []string{"key0=val0", "key1=val1"}, - } - if err := command.ParseFlags([]string{ - expected.blobPath, - "--key", expected.Key, - "--signature-format", expected.SignerFlagOpts.SignatureFormat, - "--expiry", expected.expiry.String(), - "--plugin-config", "key0=val0", - "--plugin-config", "key1=val1"}); err != nil { - t.Fatalf("Parse Flag failed: %v", err) - } - if err := command.Args(command, command.Flags().Args()); err != nil { - t.Fatalf("Parse args failed: %v", err) - } - if !reflect.DeepEqual(*expected, *opts) { - t.Fatalf("Expect sign blob opts: %v, got: %v", expected, opts) - } - config, err := cmd.ParseFlagMap(opts.pluginConfig, cmd.PflagPluginConfig.Name) - if err != nil { - t.Fatalf("Parse plugin Config flag failed: %v", err) - } - if len(config) != 2 { - t.Fatalf("Expect plugin config number: %v, got: %v ", 2, len(config)) - } - for i := 0; i < 2; i++ { - key, val := fmt.Sprintf("key%v", i), fmt.Sprintf("val%v", i) - configVal, ok := config[key] - if !ok { - t.Fatalf("Key: %v not in config", key) - } - if val != configVal { - t.Fatalf("Value for key: %v error, got: %v, expect: %v", key, configVal, val) - } - } -} - -func TestBlobSignCommand_OnDemandKeyOptions(t *testing.T) { - opts := &blobOpts{} - command := blobSignCommand(opts) - expected := &blobOpts{ - blobPath: "path", - SecureFlagOpts: SecureFlagOpts{ - Username: "user", - Password: "password", - }, - SignerFlagOpts: cmd.SignerFlagOpts{ - KeyID: "keyID", - PluginName: "pluginName", - SignatureFormat: envelope.JWS, - }, - } - if err := command.ParseFlags([]string{ - expected.blobPath, - "-u", expected.Username, - "--password", expected.Password, - "--id", expected.KeyID, - "--plugin", expected.PluginName}); err != nil { - t.Fatalf("Parse Flag failed: %v", err) - } - if err := command.Args(command, command.Flags().Args()); err != nil { - t.Fatalf("Parse args failed: %v", err) - } - if !reflect.DeepEqual(*expected, *opts) { - t.Fatalf("Expect blob sign opts: %v, got: %v", expected, opts) - } -} - -func TestBlobSignCommand_OnDemandKeyBadOptions(t *testing.T) { - t.Run("error when using id and plugin options with key", func(t *testing.T) { - opts := &blobOpts{} - command := blobSignCommand(opts) - expected := &blobOpts{ - blobPath: "path", - SecureFlagOpts: SecureFlagOpts{ - Username: "user", - Password: "password", - }, - SignerFlagOpts: cmd.SignerFlagOpts{ - KeyID: "keyID", - PluginName: "pluginName", - Key: "keyName", - SignatureFormat: envelope.JWS, - }, - } - if err := command.ParseFlags([]string{ - expected.blobPath, - "-u", expected.Username, - "--password", expected.Password, - "--id", expected.KeyID, - "--plugin", expected.PluginName, - "--key", expected.Key}); err != nil { - t.Fatalf("Parse Flag failed: %v", err) - } - if err := command.Args(command, command.Flags().Args()); err != nil { - t.Fatalf("Parse args failed: %v", err) - } - if !reflect.DeepEqual(*expected, *opts) { - t.Fatalf("Expect blob sign opts: %v, got: %v", expected, opts) - } - err := command.ValidateFlagGroups() - if err == nil || err.Error() != "if any flags in the group [key id] are set none of the others can be; [id key] were all set" { - t.Fatalf("Didn't get the expected error, but got: %v", err) - } - }) - t.Run("error when using key and id options", func(t *testing.T) { - opts := &blobOpts{} - command := blobSignCommand(opts) - expected := &blobOpts{ - blobPath: "path", - SecureFlagOpts: SecureFlagOpts{ - Username: "user", - Password: "password", - }, - SignerFlagOpts: cmd.SignerFlagOpts{ - KeyID: "keyID", - Key: "keyName", - SignatureFormat: envelope.JWS, - }, - } - if err := command.ParseFlags([]string{ - expected.blobPath, - "-u", expected.Username, - "--password", expected.Password, - "--id", expected.KeyID, - "--key", expected.Key}); err != nil { - t.Fatalf("Parse Flag failed: %v", err) - } - if err := command.Args(command, command.Flags().Args()); err != nil { - t.Fatalf("Parse args failed: %v", err) - } - if !reflect.DeepEqual(*expected, *opts) { - t.Fatalf("Expect blob sign opts: %v, got: %v", expected, opts) - } - err := command.ValidateFlagGroups() - if err == nil || err.Error() != "if any flags in the group [id plugin] are set they must all be set; missing [plugin]" { - t.Fatalf("Didn't get the expected error, but got: %v", err) - } - }) - t.Run("error when using key and plugin options", func(t *testing.T) { - opts := &blobOpts{} - command := blobSignCommand(opts) - expected := &blobOpts{ - blobPath: "path", - SecureFlagOpts: SecureFlagOpts{ - Username: "user", - Password: "password", - }, - SignerFlagOpts: cmd.SignerFlagOpts{ - PluginName: "pluginName", - Key: "keyName", - SignatureFormat: envelope.JWS, - }, - } - if err := command.ParseFlags([]string{ - expected.blobPath, - "-u", expected.Username, - "--password", expected.Password, - "--plugin", expected.PluginName, - "--key", expected.Key}); err != nil { - t.Fatalf("Parse Flag failed: %v", err) - } - if err := command.Args(command, command.Flags().Args()); err != nil { - t.Fatalf("Parse args failed: %v", err) - } - if !reflect.DeepEqual(*expected, *opts) { - t.Fatalf("Expect blob sign opts: %v, got: %v", expected, opts) - } - err := command.ValidateFlagGroups() - if err == nil || err.Error() != "if any flags in the group [id plugin] are set they must all be set; missing [id]" { - t.Fatalf("Didn't get the expected error, but got: %v", err) - } - }) - t.Run("error when using id option and not plugin", func(t *testing.T) { - opts := &blobOpts{} - command := blobSignCommand(opts) - expected := &blobOpts{ - blobPath: "path", - SecureFlagOpts: SecureFlagOpts{ - Username: "user", - Password: "password", - }, - SignerFlagOpts: cmd.SignerFlagOpts{ - KeyID: "keyID", - SignatureFormat: envelope.JWS, - }, - } - if err := command.ParseFlags([]string{ - expected.blobPath, - "-u", expected.Username, - "--password", expected.Password, - "--id", expected.KeyID}); err != nil { - t.Fatalf("Parse Flag failed: %v", err) - } - if err := command.Args(command, command.Flags().Args()); err != nil { - t.Fatalf("Parse args failed: %v", err) - } - if !reflect.DeepEqual(*expected, *opts) { - t.Fatalf("Expect blob sign opts: %v, got: %v", expected, opts) - } - err := command.ValidateFlagGroups() - if err == nil || err.Error() != "if any flags in the group [id plugin] are set they must all be set; missing [plugin]" { - t.Fatalf("Didn't get the expected error, but got: %v", err) - } - }) - t.Run("error when using plugin option and not id", func(t *testing.T) { - opts := &blobOpts{} - command := blobSignCommand(opts) - expected := &blobOpts{ - blobPath: "path", - SecureFlagOpts: SecureFlagOpts{ - Username: "user", - Password: "password", - }, - SignerFlagOpts: cmd.SignerFlagOpts{ - PluginName: "pluginName", - SignatureFormat: envelope.JWS, - }, - } - if err := command.ParseFlags([]string{ - expected.blobPath, - "-u", expected.Username, - "--password", expected.Password, - "--plugin", expected.PluginName}); err != nil { - t.Fatalf("Parse Flag failed: %v", err) - } - if err := command.Args(command, command.Flags().Args()); err != nil { - t.Fatalf("Parse args failed: %v", err) - } - if !reflect.DeepEqual(*expected, *opts) { - t.Fatalf("Expect blob sign opts: %v, got: %v", expected, opts) - } - err := command.ValidateFlagGroups() - if err == nil || err.Error() != "if any flags in the group [id plugin] are set they must all be set; missing [id]" { - t.Fatalf("Didn't get the expected error, but got: %v", err) - } - }) -} - -func TestBlobSignCommand_MissingArgs(t *testing.T) { - cmd := blobSignCommand(nil) - if err := cmd.ParseFlags(nil); err != nil { - t.Fatalf("Parse Flag failed: %v", err) - } - if err := cmd.Args(cmd, cmd.Flags().Args()); err == nil { - t.Fatal("Parse Args expected error, but ok") - } -} - -func TestBlobInspectCommand_SecretsFromArgs(t *testing.T) { - opts := &blobOpts{} - command := blobInspectCommand(opts) - expected := &blobOpts{ - signaturePath: "path", - SecureFlagOpts: SecureFlagOpts{ - Password: "password", - InsecureRegistry: true, - Username: "user", - }, - outputFormat: cmd.OutputPlaintext} - if err := command.ParseFlags([]string{ - expected.signaturePath, - "--password", expected.Password, - "-u", expected.Username, - "--insecure-registry", - "--output", "text"}); err != nil { - t.Fatalf("Parse Flag failed: %v", err) - } - if err := command.Args(command, command.Flags().Args()); err != nil { - t.Fatalf("Parse Args failed: %v", err) - } - if !reflect.DeepEqual(*expected, *opts) { - t.Fatalf("Expect blob inspect opts: %v, got: %v", expected, opts) - } -} - -func TestBlobInspectCommand_SecretsFromEnv(t *testing.T) { - t.Setenv(defaultUsernameEnv, "user") - t.Setenv(defaultPasswordEnv, "password") - opts := &blobOpts{} - expected := &blobOpts{ - signaturePath: "path", - SecureFlagOpts: SecureFlagOpts{ - Password: "password", - Username: "user", - }, - outputFormat: cmd.OutputJSON, - } - command := blobInspectCommand(opts) - if err := command.ParseFlags([]string{ - expected.signaturePath, - "--output", "json"}); err != nil { - t.Fatalf("Parse Flag failed: %v", err) - } - if err := command.Args(command, command.Flags().Args()); err != nil { - t.Fatalf("Parse Args failed: %v", err) - } - if !reflect.DeepEqual(*expected, *opts) { - t.Fatalf("Expect blob inspect opts: %v, got: %v", expected, opts) - } -} - -func TestBlobInspectCommand_MissingArgs(t *testing.T) { - command := blobInspectCommand(nil) - if err := command.ParseFlags(nil); err != nil { - t.Fatalf("Parse Flag failed: %v", err) - } - if err := command.Args(command, command.Flags().Args()); err == nil { - t.Fatal("Parse Args expected error, but ok") - } -} diff --git a/cmd/notation/inspect.go b/cmd/notation/inspect.go index a92b0c083..28d08b9b4 100644 --- a/cmd/notation/inspect.go +++ b/cmd/notation/inspect.go @@ -14,27 +14,22 @@ package main import ( - "crypto/sha256" - b64 "encoding/base64" - "encoding/hex" "errors" "fmt" - "os" - "strconv" - "strings" - "time" - "github.com/notaryproject/notation-core-go/signature" "github.com/notaryproject/notation-go/plugin/proto" "github.com/notaryproject/notation-go/registry" cmderr "github.com/notaryproject/notation/cmd/notation/internal/errors" "github.com/notaryproject/notation/cmd/notation/internal/experimental" + "github.com/notaryproject/notation/cmd/notation/internal/sharedutils" "github.com/notaryproject/notation/internal/cmd" "github.com/notaryproject/notation/internal/envelope" "github.com/notaryproject/notation/internal/ioutil" "github.com/notaryproject/notation/internal/tree" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/spf13/cobra" + "os" + "strconv" ) type inspectOpts struct { @@ -46,29 +41,6 @@ type inspectOpts struct { maxSignatures int } -type inspectOutput struct { - MediaType string `json:"mediaType"` - Signatures []signatureOutput -} - -type signatureOutput struct { - MediaType string `json:"mediaType"` - Digest string `json:"digest"` - SignatureAlgorithm string `json:"signatureAlgorithm"` - SignedAttributes map[string]string `json:"signedAttributes"` - UserDefinedAttributes map[string]string `json:"userDefinedAttributes"` - UnsignedAttributes map[string]string `json:"unsignedAttributes"` - Certificates []certificateOutput `json:"certificates"` - SignedArtifact ocispec.Descriptor `json:"signedArtifact"` -} - -type certificateOutput struct { - SHA256Fingerprint string `json:"SHA256Fingerprint"` - IssuedTo string `json:"issuedTo"` - IssuedBy string `json:"issuedBy"` - Expiry string `json:"expiry"` -} - func inspectCommand(opts *inspectOpts) *cobra.Command { if opts == nil { opts = &inspectOpts{} @@ -137,7 +109,7 @@ func runInspect(command *cobra.Command, opts *inspectOpts) error { if err != nil { return err } - output := inspectOutput{MediaType: manifestDesc.MediaType, Signatures: []signatureOutput{}} + output := sharedutils.InspectOutput{MediaType: manifestDesc.MediaType, Signatures: []sharedutils.SignatureOutput{}} skippedSignatures := false err = listSignatures(ctx, sigRepo, manifestDesc, opts.maxSignatures, func(sigManifestDesc ocispec.Descriptor) error { sigBlob, sigDesc, err := sigRepo.FetchSignatureBlob(ctx, sigManifestDesc) @@ -149,40 +121,40 @@ func runInspect(command *cobra.Command, opts *inspectOpts) error { sigEnvelope, err := signature.ParseEnvelope(sigDesc.MediaType, sigBlob) if err != nil { - logSkippedSignature(sigManifestDesc, err) + sharedutils.LogSkippedSignature(sigManifestDesc, err) skippedSignatures = true return nil } envelopeContent, err := sigEnvelope.Content() if err != nil { - logSkippedSignature(sigManifestDesc, err) + sharedutils.LogSkippedSignature(sigManifestDesc, err) skippedSignatures = true return nil } signedArtifactDesc, err := envelope.DescriptorFromSignaturePayload(&envelopeContent.Payload) if err != nil { - logSkippedSignature(sigManifestDesc, err) + sharedutils.LogSkippedSignature(sigManifestDesc, err) skippedSignatures = true return nil } signatureAlgorithm, err := proto.EncodeSigningAlgorithm(envelopeContent.SignerInfo.SignatureAlgorithm) if err != nil { - logSkippedSignature(sigManifestDesc, err) + sharedutils.LogSkippedSignature(sigManifestDesc, err) skippedSignatures = true return nil } - sig := signatureOutput{ + sig := sharedutils.SignatureOutput{ MediaType: sigDesc.MediaType, Digest: sigManifestDesc.Digest.String(), SignatureAlgorithm: string(signatureAlgorithm), - SignedAttributes: getSignedAttributes(opts.outputFormat, envelopeContent), + SignedAttributes: sharedutils.GetSignedAttributes(opts.outputFormat, envelopeContent), UserDefinedAttributes: signedArtifactDesc.Annotations, - UnsignedAttributes: getUnsignedAttributes(envelopeContent), - Certificates: getCertificates(opts.outputFormat, envelopeContent), + UnsignedAttributes: sharedutils.GetUnsignedAttributes(envelopeContent), + Certificates: sharedutils.GetCertificates(opts.outputFormat, envelopeContent), SignedArtifact: *signedArtifactDesc, } @@ -214,71 +186,7 @@ func runInspect(command *cobra.Command, opts *inspectOpts) error { return nil } -func logSkippedSignature(sigDesc ocispec.Descriptor, err error) { - fmt.Fprintf(os.Stderr, "Warning: Skipping signature %s because of error: %v\n", sigDesc.Digest.String(), err) -} - -func getSignedAttributes(outputFormat string, envContent *signature.EnvelopeContent) map[string]string { - signedAttributes := map[string]string{ - "signingScheme": string(envContent.SignerInfo.SignedAttributes.SigningScheme), - "signingTime": formatTimestamp(outputFormat, envContent.SignerInfo.SignedAttributes.SigningTime), - } - expiry := envContent.SignerInfo.SignedAttributes.Expiry - if !expiry.IsZero() { - signedAttributes["expiry"] = formatTimestamp(outputFormat, expiry) - } - - for _, attribute := range envContent.SignerInfo.SignedAttributes.ExtendedAttributes { - signedAttributes[fmt.Sprint(attribute.Key)] = fmt.Sprint(attribute.Value) - } - - return signedAttributes -} - -func getUnsignedAttributes(envContent *signature.EnvelopeContent) map[string]string { - unsignedAttributes := map[string]string{} - - if envContent.SignerInfo.UnsignedAttributes.TimestampSignature != nil { - unsignedAttributes["timestampSignature"] = b64.StdEncoding.EncodeToString(envContent.SignerInfo.UnsignedAttributes.TimestampSignature) - } - - if envContent.SignerInfo.UnsignedAttributes.SigningAgent != "" { - unsignedAttributes["signingAgent"] = envContent.SignerInfo.UnsignedAttributes.SigningAgent - } - - return unsignedAttributes -} - -func formatTimestamp(outputFormat string, t time.Time) string { - switch outputFormat { - case cmd.OutputJSON: - return t.Format(time.RFC3339) - default: - return t.Format(time.ANSIC) - } -} - -func getCertificates(outputFormat string, envContent *signature.EnvelopeContent) []certificateOutput { - certificates := []certificateOutput{} - - for _, cert := range envContent.SignerInfo.CertificateChain { - h := sha256.Sum256(cert.Raw) - fingerprint := strings.ToLower(hex.EncodeToString(h[:])) - - certificate := certificateOutput{ - SHA256Fingerprint: fingerprint, - IssuedTo: cert.Subject.String(), - IssuedBy: cert.Issuer.String(), - Expiry: formatTimestamp(outputFormat, cert.NotAfter), - } - - certificates = append(certificates, certificate) - } - - return certificates -} - -func printOutput(outputFormat string, ref string, output inspectOutput) error { +func printOutput(outputFormat string, ref string, output sharedutils.InspectOutput) error { if outputFormat == cmd.OutputJSON { return ioutil.PrintObjectAsJSON(output) } @@ -298,13 +206,13 @@ func printOutput(outputFormat string, ref string, output inspectOutput) error { sigNode.AddPair("signature algorithm", signature.SignatureAlgorithm) signedAttributesNode := sigNode.Add("signed attributes") - addMapToTree(signedAttributesNode, signature.SignedAttributes) + sharedutils.AddMapToTree(signedAttributesNode, signature.SignedAttributes) userDefinedAttributesNode := sigNode.Add("user defined attributes") - addMapToTree(userDefinedAttributesNode, signature.UserDefinedAttributes) + sharedutils.AddMapToTree(userDefinedAttributesNode, signature.UserDefinedAttributes) unsignedAttributesNode := sigNode.Add("unsigned attributes") - addMapToTree(unsignedAttributesNode, signature.UnsignedAttributes) + sharedutils.AddMapToTree(unsignedAttributesNode, signature.UnsignedAttributes) certListNode := sigNode.Add("certificates") for _, cert := range signature.Certificates { @@ -323,13 +231,3 @@ func printOutput(outputFormat string, ref string, output inspectOutput) error { root.Print() return nil } - -func addMapToTree(node *tree.Node, m map[string]string) { - if len(m) > 0 { - for k, v := range m { - node.AddPair(k, v) - } - } else { - node.Add("(empty)") - } -} diff --git a/cmd/notation/internal/sharedutils/inspect.go b/cmd/notation/internal/sharedutils/inspect.go new file mode 100644 index 000000000..d3e4e2831 --- /dev/null +++ b/cmd/notation/internal/sharedutils/inspect.go @@ -0,0 +1,125 @@ +// Copyright The Notary Project Authors. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sharedutils + +import ( + "crypto/sha256" + b64 "encoding/base64" + "encoding/hex" + "fmt" + "github.com/notaryproject/notation-core-go/signature" + "github.com/notaryproject/notation/internal/cmd" + "github.com/notaryproject/notation/internal/tree" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "os" + "strings" + "time" +) + +type InspectOutput struct { + MediaType string `json:"mediaType"` + Signatures []SignatureOutput +} + +type SignatureOutput struct { + MediaType string `json:"mediaType"` + Digest string `json:"digest"` + SignatureAlgorithm string `json:"signatureAlgorithm"` + SignedAttributes map[string]string `json:"signedAttributes"` + UserDefinedAttributes map[string]string `json:"userDefinedAttributes"` + UnsignedAttributes map[string]string `json:"unsignedAttributes"` + Certificates []certificateOutput `json:"certificates"` + SignedArtifact ocispec.Descriptor `json:"signedArtifact"` +} + +type certificateOutput struct { + SHA256Fingerprint string `json:"SHA256Fingerprint"` + IssuedTo string `json:"issuedTo"` + IssuedBy string `json:"issuedBy"` + Expiry string `json:"expiry"` +} + +func GetSignedAttributes(outputFormat string, envContent *signature.EnvelopeContent) map[string]string { + signedAttributes := map[string]string{ + "signingScheme": string(envContent.SignerInfo.SignedAttributes.SigningScheme), + "signingTime": formatTimestamp(outputFormat, envContent.SignerInfo.SignedAttributes.SigningTime), + } + expiry := envContent.SignerInfo.SignedAttributes.Expiry + if !expiry.IsZero() { + signedAttributes["expiry"] = formatTimestamp(outputFormat, expiry) + } + + for _, attribute := range envContent.SignerInfo.SignedAttributes.ExtendedAttributes { + signedAttributes[fmt.Sprint(attribute.Key)] = fmt.Sprint(attribute.Value) + } + + return signedAttributes +} + +func GetUnsignedAttributes(envContent *signature.EnvelopeContent) map[string]string { + unsignedAttributes := map[string]string{} + + if envContent.SignerInfo.UnsignedAttributes.TimestampSignature != nil { + unsignedAttributes["timestampSignature"] = b64.StdEncoding.EncodeToString(envContent.SignerInfo.UnsignedAttributes.TimestampSignature) + } + + if envContent.SignerInfo.UnsignedAttributes.SigningAgent != "" { + unsignedAttributes["signingAgent"] = envContent.SignerInfo.UnsignedAttributes.SigningAgent + } + + return unsignedAttributes +} + +func formatTimestamp(outputFormat string, t time.Time) string { + switch outputFormat { + case cmd.OutputJSON: + return t.Format(time.RFC3339) + default: + return t.Format(time.ANSIC) + } +} + +func GetCertificates(outputFormat string, envContent *signature.EnvelopeContent) []certificateOutput { + certificates := []certificateOutput{} + + for _, cert := range envContent.SignerInfo.CertificateChain { + h := sha256.Sum256(cert.Raw) + fingerprint := strings.ToLower(hex.EncodeToString(h[:])) + + certificate := certificateOutput{ + SHA256Fingerprint: fingerprint, + IssuedTo: cert.Subject.String(), + IssuedBy: cert.Issuer.String(), + Expiry: formatTimestamp(outputFormat, cert.NotAfter), + } + + certificates = append(certificates, certificate) + } + + return certificates +} + +func LogSkippedSignature(sigDesc ocispec.Descriptor, err error) { + fmt.Fprintf(os.Stderr, "Warning: Skipping signature %s because of error: %v\n", sigDesc.Digest.String(), err) +} + +func AddMapToTree(node *tree.Node, m map[string]string) { + if len(m) > 0 { + for k, v := range m { + node.AddPair(k, v) + } + } else { + node.Add("(empty)") + } +} diff --git a/cmd/notation/main.go b/cmd/notation/main.go index 848a865ff..faa8882c4 100644 --- a/cmd/notation/main.go +++ b/cmd/notation/main.go @@ -10,12 +10,12 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. - package main import ( "os" + "github.com/notaryproject/notation/cmd/notation/blob" "github.com/notaryproject/notation/cmd/notation/cert" "github.com/notaryproject/notation/cmd/notation/policy" "github.com/spf13/cobra" @@ -37,6 +37,7 @@ func main() { signCommand(nil), verifyCommand(nil), listCommand(nil), + blob.Cmd(), cert.Cmd(), policy.Cmd(), keyCommand(), @@ -45,8 +46,6 @@ func main() { logoutCommand(nil), versionCommand(), inspectCommand(nil), - blobSignCommand(nil), - blobInspectCommand(nil), ) if err := cmd.Execute(); err != nil { os.Exit(1) diff --git a/cmd/notation/notation b/cmd/notation/notation deleted file mode 100755 index 387ca5f0d..000000000 Binary files a/cmd/notation/notation and /dev/null differ