diff --git a/cmd/cosign/cli/commands.go b/cmd/cosign/cli/commands.go index a8add3ef78f..d2bf1de69ac 100644 --- a/cmd/cosign/cli/commands.go +++ b/cmd/cosign/cli/commands.go @@ -106,7 +106,6 @@ func New() *cobra.Command { cmd.AddCommand(Manifest()) cmd.AddCommand(PIVTool()) cmd.AddCommand(PKCS11Tool()) - cmd.AddCommand(Policy()) cmd.AddCommand(PublicKey()) cmd.AddCommand(Save()) cmd.AddCommand(Sign()) diff --git a/cmd/cosign/cli/options/policy.go b/cmd/cosign/cli/options/policy.go deleted file mode 100644 index 4ecdc9f35c1..00000000000 --- a/cmd/cosign/cli/options/policy.go +++ /dev/null @@ -1,95 +0,0 @@ -// -// Copyright 2021 The Sigstore 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 options - -import ( - "github.com/spf13/cobra" -) - -// PolicyInitOptions is the top level wrapper for the policy-init command. -type PolicyInitOptions struct { - ImageRef string - Maintainers []string - Issuer string - Threshold int - Expires int - OutFile string - Registry RegistryOptions -} - -var _ Interface = (*PolicyInitOptions)(nil) - -// AddFlags implements Interface -func (o *PolicyInitOptions) AddFlags(cmd *cobra.Command) { - cmd.Flags().StringVar(&o.ImageRef, "namespace", "ns", - "registry namespace that the root policy belongs to") - - cmd.Flags().StringVar(&o.OutFile, "out", "o", - "output policy locally") - _ = cmd.Flags().SetAnnotation("out", cobra.BashCompSubdirsInDir, []string{}) - - cmd.Flags().StringVar(&o.Issuer, "issuer", "", - "trusted issuer to use for identity tokens, e.g. https://accounts.google.com") - - cmd.Flags().IntVar(&o.Threshold, "threshold", 1, - "threshold for root policy signers") - - cmd.Flags().StringSliceVarP(&o.Maintainers, "maintainers", "m", nil, - "list of maintainers to add to the root policy") - - cmd.Flags().IntVar(&o.Expires, "expires", 0, - "total expire duration in days") - - o.Registry.AddFlags(cmd) -} - -type PolicySignOptions struct { - ImageRef string - OutFile string - Registry RegistryOptions - Fulcio FulcioOptions - Rekor RekorOptions - SkipConfirmation bool - TlogUpload bool - TSAServerURL string - - OIDC OIDCOptions -} - -var _ Interface = (*PolicySignOptions)(nil) - -// AddFlags implements Interface -func (o *PolicySignOptions) AddFlags(cmd *cobra.Command) { - cmd.Flags().StringVar(&o.ImageRef, "namespace", "ns", - "registry namespace that the root policy belongs to") - - cmd.Flags().StringVar(&o.OutFile, "out", "o", - "output policy locally") - - cmd.Flags().BoolVarP(&o.SkipConfirmation, "yes", "y", false, - "skip confirmation prompts for non-destructive operations") - - cmd.Flags().BoolVar(&o.TlogUpload, "tlog-upload", true, - "whether or not to upload to the tlog") - - cmd.Flags().StringVar(&o.TSAServerURL, "timestamp-server-url", "", - "url to the Timestamp RFC3161 server, default none. Must be the path to the API to request timestamp responses, e.g. https://freetsa.org/tsr") - - o.Registry.AddFlags(cmd) - o.Fulcio.AddFlags(cmd) - o.Rekor.AddFlags(cmd) - o.OIDC.AddFlags(cmd) -} diff --git a/cmd/cosign/cli/policy_init.go b/cmd/cosign/cli/policy_init.go deleted file mode 100644 index b0e407094e7..00000000000 --- a/cmd/cosign/cli/policy_init.go +++ /dev/null @@ -1,336 +0,0 @@ -// -// Copyright 2021 The Sigstore 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 cli - -import ( - "bytes" - "context" - "crypto/sha256" - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "io" - "net/mail" - "os" - "path/filepath" - "strings" - "time" - - "github.com/google/go-containerregistry/pkg/authn" - "github.com/google/go-containerregistry/pkg/name" - "github.com/google/go-containerregistry/pkg/v1/remote" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/rekor" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/sign" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/upload" - "github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa" - "github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa/client" - "github.com/sigstore/sigstore/pkg/cryptoutils" - - "github.com/sigstore/cosign/v2/pkg/cosign" - cremote "github.com/sigstore/cosign/v2/pkg/cosign/remote" - "github.com/sigstore/cosign/v2/pkg/sget" //nolint:staticcheck - sigs "github.com/sigstore/cosign/v2/pkg/signature" - signatureoptions "github.com/sigstore/sigstore/pkg/signature/options" - "github.com/sigstore/sigstore/pkg/tuf" - "github.com/spf13/cobra" -) - -func validEmail(email string) bool { - _, err := mail.ParseAddress(email) - return err == nil -} - -func rootPath(imageRef string) string { - return filepath.Join(imageRef, "root") -} - -func Policy() *cobra.Command { - cmd := &cobra.Command{ - Use: "policy", - Short: "subcommand to manage a keyless policy.", - Long: "policy is used to manage a root.json policy\nfor keyless signing delegation. This is used to establish a policy for a registry namespace,\na signing threshold and a list of maintainers who can sign over the body section.", - RunE: func(cmd *cobra.Command, args []string) error { - return cmd.Help() - }, - } - - cmd.AddCommand( - initPolicy(), - signPolicy(), - ) - - return cmd -} - -func initPolicy() *cobra.Command { - o := &options.PolicyInitOptions{} - - cmd := &cobra.Command{ - Use: "init", - Short: "generate a new keyless policy.", - Long: "init is used to generate a root.json policy\nfor keyless signing delegation. This is used to establish a policy for a registry namespace,\na signing threshold and a list of maintainers who can sign over the body section.", - Example: ` - # extract public key from private key to a specified out file. - cosign policy init --namespace --maintainers {email_addresses} --threshold --expires (days)`, - PersistentPreRun: options.BindViper, - RunE: func(cmd *cobra.Command, args []string) error { - var publicKeys []*tuf.Key - - // Process the list of maintainers by - // 1. Ensure each entry is a correctly formatted email address - // 2. If 1 is true, then remove surplus whitespace (caused by gaps between commas) - for _, email := range o.Maintainers { - if !validEmail(email) { - panic(fmt.Sprintf("Invalid email format: %s", email)) - } else { - // Currently only a single issuer can be set for all the maintainers. - key := tuf.FulcioVerificationKey(strings.TrimSpace(email), o.Issuer) - publicKeys = append(publicKeys, key) - } - } - - // Create a new root. - root := tuf.NewRoot() - - // Add the maintainer identities to the root's trusted keys. - for _, key := range publicKeys { - root.AddKey(key) - } - - // Set root keys, threshold, and namespace. - role, ok := root.Roles["root"] - if !ok { - role = &tuf.Role{KeyIDs: []string{}, Threshold: 1} - } - role.AddKeysWithThreshold(publicKeys, o.Threshold) - root.Roles["root"] = role - root.Namespace = o.ImageRef - - if o.Expires > 0 { - root.Expires = time.Now().AddDate(0, 0, o.Expires).UTC().Round(time.Second) - } - - policy, err := root.Marshal() - if err != nil { - return err - } - policyFile, err := policy.JSONMarshal("", "\t") - if err != nil { - return err - } - - var outfile string - if o.OutFile != "" { - outfile = o.OutFile - err = os.WriteFile(o.OutFile, policyFile, 0600) - if err != nil { - return fmt.Errorf("error writing to %s: %w", outfile, err) - } - } else { - tempFile, err := os.CreateTemp("", "root") - if err != nil { - return err - } - outfile = tempFile.Name() - defer func() { - tempFile.Close() - os.Remove(tempFile.Name()) - }() - } - - files := []cremote.File{ - cremote.FileFromFlag(outfile), - } - - return upload.BlobCmd(cmd.Context(), o.Registry, files, nil, "", rootPath(o.ImageRef)) - }, - } - - o.AddFlags(cmd) - - return cmd -} - -func signPolicy() *cobra.Command { - o := &options.PolicySignOptions{} - - cmd := &cobra.Command{ - Use: "sign", - Short: "sign a keyless policy.", - Long: "policy is used to manage a root.json policy\nfor keyless signing delegation. This is used to establish a policy for a registry namespace,\na signing threshold and a list of maintainers who can sign over the body section.", - PersistentPreRun: options.BindViper, - RunE: func(cmd *cobra.Command, args []string) error { - ctx := cmd.Context() - if ro.Timeout != 0 { - var cancelFn context.CancelFunc - ctx, cancelFn = context.WithTimeout(ctx, ro.Timeout) - defer cancelFn() - } - - // Get Fulcio signer - oidcClientSecret, err := o.OIDC.ClientSecret() - if err != nil { - return err - } - ko := options.KeyOpts{ - FulcioURL: o.Fulcio.URL, - IDToken: o.Fulcio.IdentityToken, - InsecureSkipFulcioVerify: o.Fulcio.InsecureSkipFulcioVerify, - RekorURL: o.Rekor.URL, - OIDCIssuer: o.OIDC.Issuer, - OIDCClientID: o.OIDC.ClientID, - OIDCClientSecret: oidcClientSecret, - OIDCRedirectURL: o.OIDC.RedirectURL, - OIDCProvider: o.OIDC.Provider, - SkipConfirmation: o.SkipConfirmation, - TSAServerURL: o.TSAServerURL, - } - sv, err := sign.SignerFromKeyOpts(ctx, "", "", ko) - - if err != nil { - return err - } - defer sv.Close() - - certs, err := cryptoutils.LoadCertificatesFromPEM(bytes.NewReader(sv.Cert)) - if err != nil { - return err - } - if len(certs) == 0 || certs[0].EmailAddresses == nil { - return errors.New("error decoding certificate") - } - signerEmail := sigs.CertSubject(certs[0]) - ce := cosign.CertExtensions{Cert: certs[0]} - signerIssuer := ce.GetIssuer() - - // Retrieve root.json from registry. - imgName := rootPath(o.ImageRef) - ref, err := name.ParseReference(imgName, o.Registry.NameOptions()...) - if err != nil { - return err - } - opts := []remote.Option{ - remote.WithAuthFromKeychain(authn.DefaultKeychain), - remote.WithContext(ctx), - } - - img, err := remote.Image(ref, opts...) - if err != nil { - return err - } - dgst, err := img.Digest() - if err != nil { - return err - } - - result := &bytes.Buffer{} - if err := sget.New(imgName+"@"+dgst.String(), "", o.Rekor.URL, result).Do(ctx); err != nil { - return fmt.Errorf("error getting result: %w", err) - } - b, err := io.ReadAll(result) - if err != nil { - return fmt.Errorf("error reading bytes from root.json: %w", err) - } - - // Unmarshal policy and verify that Fulcio signer email is in the trusted - signed := &tuf.Signed{} - if err := json.Unmarshal(b, signed); err != nil { - return fmt.Errorf("unmarshalling signed root policy: %w", err) - } - - // Create and add signature - key := tuf.FulcioVerificationKey(signerEmail, signerIssuer) - sig, err := sv.SignMessage(bytes.NewReader(signed.Signed), signatureoptions.WithContext(ctx)) - if err != nil { - return fmt.Errorf("error occurred while during artifact signing): %w", err) - } - signature := tuf.Signature{ - Signature: base64.StdEncoding.EncodeToString(sig), - Cert: base64.StdEncoding.EncodeToString(sv.Cert), - } - if err := signed.AddOrUpdateSignature(key, signature); err != nil { - return err - } - - if o.TSAServerURL != "" { - // Here we get the response from the timestamped authority server - if _, err := tsa.GetTimestampedSignature(signed.Signed, client.NewTSAClient(o.TSAServerURL)); err != nil { - return err - } - } - - // Upload to rekor - shouldUpload, err := sign.ShouldUploadToTlog(ctx, ko, ref, o.TlogUpload) - if err != nil { - return fmt.Errorf("should upload to tlog: %w", err) - } - if shouldUpload { - // TODO: Refactor with sign.go - rekorBytes := sv.Cert - rekorClient, err := rekor.NewClient(o.Rekor.URL) - if err != nil { - return err - } - checkSum := sha256.New() - if _, err := checkSum.Write(signed.Signed); err != nil { - return err - } - entry, err := cosign.TLogUpload(ctx, rekorClient, sig, checkSum, rekorBytes) - if err != nil { - return err - } - fmt.Fprintln(os.Stderr, "tlog entry created with index:", *entry.LogIndex) - } - - // Push updated root.json to the registry - policyFile, err := signed.JSONMarshal("", "\t") - if err != nil { - return err - } - - var outfile string - if o.OutFile != "" { - outfile = o.OutFile - err = os.WriteFile(o.OutFile, policyFile, 0600) - if err != nil { - return fmt.Errorf("error writing to %s: %w", outfile, err) - } - } else { - tempFile, err := os.CreateTemp("", "root") - if err != nil { - return err - } - outfile = tempFile.Name() - defer func() { - tempFile.Close() - os.Remove(tempFile.Name()) - }() - } - - files := []cremote.File{ - cremote.FileFromFlag(outfile), - } - - return upload.BlobCmd(ctx, o.Registry, files, nil, "", rootPath(o.ImageRef)) - }, - } - - o.AddFlags(cmd) - - return cmd -} diff --git a/cmd/cosign/cli/policy_init_test.go b/cmd/cosign/cli/policy_init_test.go deleted file mode 100644 index 1d0fbea850f..00000000000 --- a/cmd/cosign/cli/policy_init_test.go +++ /dev/null @@ -1,33 +0,0 @@ -// -// Copyright 2021 The Sigstore 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 cli - -import ( - "testing" -) - -// Tests correctly formatted emails do not fail validEmail call -// Tests incorrectly formatted emails do not pass validEmail call -func TestEmailValid(t *testing.T) { - goodEmail := "foo@foo.com" - strongBadEmail := "foofoocom" - - if !validEmail(goodEmail) { - t.Errorf("correct email %s, failed valid check", goodEmail) - } else if validEmail(strongBadEmail) { - t.Errorf("bad email %s, passed valid check", strongBadEmail) - } -} diff --git a/doc/cosign.md b/doc/cosign.md index 61f0675a45e..c3eec43dffa 100644 --- a/doc/cosign.md +++ b/doc/cosign.md @@ -31,7 +31,6 @@ A tool for Container Signing, Verification and Storage in an OCI registry. * [cosign manifest](cosign_manifest.md) - Provides utilities for discovering images in and performing operations on Kubernetes manifests * [cosign piv-tool](cosign_piv-tool.md) - Provides utilities for managing a hardware token * [cosign pkcs11-tool](cosign_pkcs11-tool.md) - Provides utilities for retrieving information from a PKCS11 token. -* [cosign policy](cosign_policy.md) - subcommand to manage a keyless policy. * [cosign public-key](cosign_public-key.md) - Gets a public key from the key-pair. * [cosign save](cosign_save.md) - Save the container image and associated signatures to disk at the specified directory. * [cosign sign](cosign_sign.md) - Sign the supplied container image. diff --git a/doc/cosign_policy.md b/doc/cosign_policy.md deleted file mode 100644 index 64b30847027..00000000000 --- a/doc/cosign_policy.md +++ /dev/null @@ -1,34 +0,0 @@ -## cosign policy - -subcommand to manage a keyless policy. - -### Synopsis - -policy is used to manage a root.json policy -for keyless signing delegation. This is used to establish a policy for a registry namespace, -a signing threshold and a list of maintainers who can sign over the body section. - -``` -cosign policy [flags] -``` - -### Options - -``` - -h, --help help for policy -``` - -### Options inherited from parent commands - -``` - --output-file string log output to a file - -t, --timeout duration timeout for commands (default 3m0s) - -d, --verbose log debug output -``` - -### SEE ALSO - -* [cosign](cosign.md) - A tool for Container Signing, Verification and Storage in an OCI registry. -* [cosign policy init](cosign_policy_init.md) - generate a new keyless policy. -* [cosign policy sign](cosign_policy_sign.md) - sign a keyless policy. - diff --git a/doc/cosign_policy_init.md b/doc/cosign_policy_init.md deleted file mode 100644 index 993fb9dec0d..00000000000 --- a/doc/cosign_policy_init.md +++ /dev/null @@ -1,50 +0,0 @@ -## cosign policy init - -generate a new keyless policy. - -### Synopsis - -init is used to generate a root.json policy -for keyless signing delegation. This is used to establish a policy for a registry namespace, -a signing threshold and a list of maintainers who can sign over the body section. - -``` -cosign policy init [flags] -``` - -### Examples - -``` - - # extract public key from private key to a specified out file. - cosign policy init --namespace --maintainers {email_addresses} --threshold --expires (days) -``` - -### Options - -``` - --allow-http-registry whether to allow using HTTP protocol while connecting to registries. Don't use this for anything but testing - --allow-insecure-registry whether to allow insecure connections to registries (e.g., with expired or self-signed TLS certificates). Don't use this for anything but testing - --attachment-tag-prefix [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] optional custom prefix to use for attached image tags. Attachment images are tagged as: [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] - --expires int total expire duration in days - -h, --help help for init - --issuer string trusted issuer to use for identity tokens, e.g. https://accounts.google.com - --k8s-keychain whether to use the kubernetes keychain instead of the default keychain (supports workload identity). - -m, --maintainers strings list of maintainers to add to the root policy - --namespace string registry namespace that the root policy belongs to (default "ns") - --out string output policy locally (default "o") - --threshold int threshold for root policy signers (default 1) -``` - -### Options inherited from parent commands - -``` - --output-file string log output to a file - -t, --timeout duration timeout for commands (default 3m0s) - -d, --verbose log debug output -``` - -### SEE ALSO - -* [cosign policy](cosign_policy.md) - subcommand to manage a keyless policy. - diff --git a/doc/cosign_policy_sign.md b/doc/cosign_policy_sign.md deleted file mode 100644 index 1b1ce44c390..00000000000 --- a/doc/cosign_policy_sign.md +++ /dev/null @@ -1,51 +0,0 @@ -## cosign policy sign - -sign a keyless policy. - -### Synopsis - -policy is used to manage a root.json policy -for keyless signing delegation. This is used to establish a policy for a registry namespace, -a signing threshold and a list of maintainers who can sign over the body section. - -``` -cosign policy sign [flags] -``` - -### Options - -``` - --allow-http-registry whether to allow using HTTP protocol while connecting to registries. Don't use this for anything but testing - --allow-insecure-registry whether to allow insecure connections to registries (e.g., with expired or self-signed TLS certificates). Don't use this for anything but testing - --attachment-tag-prefix [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] optional custom prefix to use for attached image tags. Attachment images are tagged as: [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] - --fulcio-url string [EXPERIMENTAL] address of sigstore PKI server (default "https://fulcio.sigstore.dev") - -h, --help help for sign - --identity-token string [EXPERIMENTAL] identity token to use for certificate from fulcio. the token or a path to a file containing the token is accepted. - --insecure-skip-verify [EXPERIMENTAL] skip verifying fulcio published to the SCT (this should only be used for testing). - --k8s-keychain whether to use the kubernetes keychain instead of the default keychain (supports workload identity). - --namespace string registry namespace that the root policy belongs to (default "ns") - --oidc-client-id string [EXPERIMENTAL] OIDC client ID for application (default "sigstore") - --oidc-client-secret-file string [EXPERIMENTAL] Path to file containing OIDC client secret for application - --oidc-disable-ambient-providers [EXPERIMENTAL] Disable ambient OIDC providers. When true, ambient credentials will not be read - --oidc-issuer string [EXPERIMENTAL] OIDC provider to be used to issue ID token (default "https://oauth2.sigstore.dev/auth") - --oidc-provider string [EXPERIMENTAL] Specify the provider to get the OIDC token from (Optional). If unset, all options will be tried. Options include: [spiffe, google, github, filesystem] - --oidc-redirect-url string [EXPERIMENTAL] OIDC redirect URL (Optional). The default oidc-redirect-url is 'http://localhost:0/auth/callback'. - --out string output policy locally (default "o") - --rekor-url string [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev") - --timestamp-server-url string url to the Timestamp RFC3161 server, default none. Must be the path to the API to request timestamp responses, e.g. https://freetsa.org/tsr - --tlog-upload whether or not to upload to the tlog (default true) - -y, --yes skip confirmation prompts for non-destructive operations -``` - -### Options inherited from parent commands - -``` - --output-file string log output to a file - -t, --timeout duration timeout for commands (default 3m0s) - -d, --verbose log debug output -``` - -### SEE ALSO - -* [cosign policy](cosign_policy.md) - subcommand to manage a keyless policy. -