diff --git a/cmd/kubectl-sigstore/cli/sign.go b/cmd/kubectl-sigstore/cli/sign.go index 227c567..858c40a 100644 --- a/cmd/kubectl-sigstore/cli/sign.go +++ b/cmd/kubectl-sigstore/cli/sign.go @@ -41,6 +41,8 @@ func NewCmdSign() *cobra.Command { var applySignatureConfigMap bool var updateAnnotation bool var tarballOpt string + var allowInsecure bool + var force bool var imageAnnotations []string var rekorURL string var noTlogUpload bool @@ -54,7 +56,7 @@ func NewCmdSign() *cobra.Command { makeTarball := (tarballOpt == "yes") - err := sign(inputDir, resBundleRef, keyPath, rekorURL, output, appendSignature, applySignatureConfigMap, updateAnnotation, makeTarball, noTlogUpload, imageAnnotations) + err := sign(inputDir, resBundleRef, keyPath, rekorURL, output, appendSignature, applySignatureConfigMap, updateAnnotation, makeTarball, allowInsecure, force, noTlogUpload, imageAnnotations) if err != nil { log.Fatalf("error occurred during signing: %s", err.Error()) return nil @@ -72,6 +74,10 @@ func NewCmdSign() *cobra.Command { cmd.PersistentFlags().BoolVar(&applySignatureConfigMap, "apply-signature-configmap", false, "whether to apply a generated signature configmap only when \"output\" is k8s configmap") cmd.PersistentFlags().BoolVar(&updateAnnotation, "annotation-metadata", true, "whether to update annotation and generate signed yaml file") cmd.PersistentFlags().StringVar(&tarballOpt, "tarball", "yes", "whether to make a tarball for signing (this will be default to \"no\" in v0.5.0+)") + + // cosign cli options + cmd.PersistentFlags().BoolVar(&allowInsecure, "allow-insecure-registry", false, "whether to allow insecure connections to registries. Don't use this for anything but testing") + cmd.PersistentFlags().BoolVar(&force, "force", false, "skip warnings and confirmations") cmd.PersistentFlags().StringArrayVarP(&imageAnnotations, "annotation", "a", []string{}, "extra key=value pairs to sign") cmd.PersistentFlags().StringVar(&rekorURL, "rekor-url", "https://rekor.sigstore.dev", "URL of rekor STL server (default \"https://rekor.sigstore.dev\")") cmd.PersistentFlags().BoolVar(&noTlogUpload, "no-tlog-upload", false, "whether to not upload the transparency log") @@ -79,7 +85,7 @@ func NewCmdSign() *cobra.Command { return cmd } -func sign(inputDir, resBundleRef, keyPath, rekorURL, output string, appendSignature, applySignatureConfigMap, updateAnnotation, tarball, noTlogUpload bool, annotations []string) error { +func sign(inputDir, resBundleRef, keyPath, rekorURL, output string, appendSignature, applySignatureConfigMap, updateAnnotation, tarball, allowInsecure, noTlogUpload, force bool, annotations []string) error { if output == "" && updateAnnotation { if isDir, _ := k8smnfutil.IsDir(inputDir); isDir { // e.g.) "./yamls/" --> "./yamls/manifest.yaml.signed" @@ -114,6 +120,12 @@ func sign(inputDir, resBundleRef, keyPath, rekorURL, output string, appendSignat if noTlogUpload { so.NoTlogUpload = true } + if allowInsecure { + so.AllowInsecure = true + } + if force { + so.Force = true + } if applySignatureConfigMap && strings.HasPrefix(output, kubeutil.InClusterObjectPrefix) { so.ApplySigConfigMap = true diff --git a/cmd/kubectl-sigstore/cli/sign_test.go b/cmd/kubectl-sigstore/cli/sign_test.go index f961b3f..7b7094f 100644 --- a/cmd/kubectl-sigstore/cli/sign_test.go +++ b/cmd/kubectl-sigstore/cli/sign_test.go @@ -50,7 +50,7 @@ func TestSign(t *testing.T) { fpath := "testdata/sample-configmap.yaml" outPath := filepath.Join(tmpDir, "sample-configmap.yaml.signed") - err = sign(fpath, "", keyPath, "", outPath, false, false, true, true, false, nil) + err = sign(fpath, "", keyPath, "", outPath, false, false, true, true, false, false, false, nil) if err != nil { t.Errorf("failed to sign the test file: %s", err.Error()) return @@ -64,7 +64,7 @@ func TestSign(t *testing.T) { fpath2 := "testdata/sample-configmap-concat.yaml" outPath2 := filepath.Join(tmpDir, "sample-configmap-concat.yaml.signed") - err = sign(fpath2, "", keyPath, "", outPath2, false, false, true, true, false, nil) + err = sign(fpath2, "", keyPath, "", outPath2, false, false, true, true, false, false, false, nil) if err != nil { t.Errorf("failed to sign the test file: %s", err.Error()) return diff --git a/cmd/kubectl-sigstore/cli/verify.go b/cmd/kubectl-sigstore/cli/verify.go index 3714db7..581554e 100644 --- a/cmd/kubectl-sigstore/cli/verify.go +++ b/cmd/kubectl-sigstore/cli/verify.go @@ -39,12 +39,13 @@ func NewCmdVerify() *cobra.Command { var certChain string var rekorURL string var oidcIssuer string + var allowInsecure bool cmd := &cobra.Command{ Use: "verify -f FILENAME [-i IMAGE]", Short: "A command to verify Kubernetes YAML manifests", RunE: func(cmd *cobra.Command, args []string) error { - err := verify(filename, resBundleRef, keyPath, configPath, certRef, certChain, rekorURL, oidcIssuer) + err := verify(filename, resBundleRef, keyPath, configPath, certRef, certChain, rekorURL, oidcIssuer, allowInsecure) if err != nil { return err } @@ -62,11 +63,12 @@ func NewCmdVerify() *cobra.Command { cmd.PersistentFlags().StringVar(&certChain, "certificate-chain", "", "path to a list of CA certificates in PEM format which will be needed when building the certificate chain for the signing certificate. Must start with the parent intermediate CA certificate of the signing certificate and end with the root certificate") cmd.PersistentFlags().StringVar(&rekorURL, "rekor-url", "https://rekor.sigstore.dev", "URL of rekor STL server (default \"https://rekor.sigstore.dev\")") cmd.PersistentFlags().StringVar(&oidcIssuer, "oidc-issuer", "", "the OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth") + cmd.PersistentFlags().BoolVar(&allowInsecure, "allow-insecure-registry", false, "whether to allow insecure connections to registries. Don't use this for anything but testing") return cmd } -func verify(filename, resBundleRef, keyPath, configPath, certRef, certChain, rekorURL, oidcIssuer string) error { +func verify(filename, resBundleRef, keyPath, configPath, certRef, certChain, rekorURL, oidcIssuer string, allowInsecure bool) error { manifest, err := os.ReadFile(filename) if err != nil { fmt.Fprintln(os.Stderr, err.Error()) @@ -98,6 +100,9 @@ func verify(filename, resBundleRef, keyPath, configPath, certRef, certChain, rek if keyPath != "" { vo.KeyPath = keyPath } + if allowInsecure { + vo.AllowInsecure = true + } vo.Certificate = certRef vo.CertificateChain = certChain vo.RekorURL = rekorURL diff --git a/cmd/kubectl-sigstore/cli/verify_resource.go b/cmd/kubectl-sigstore/cli/verify_resource.go index e979863..a86e529 100644 --- a/cmd/kubectl-sigstore/cli/verify_resource.go +++ b/cmd/kubectl-sigstore/cli/verify_resource.go @@ -105,6 +105,7 @@ func NewCmdVerifyResource() *cobra.Command { var certChain string var rekorURL string var oidcIssuer string + var allowInsecure bool cmd := &cobra.Command{ Use: "verify-resource (RESOURCE/NAME | -f FILENAME | -i IMAGE)", Short: "A command to verify Kubernetes manifests of resources on cluster", @@ -135,7 +136,7 @@ func NewCmdVerifyResource() *cobra.Command { provResRef = manifestBundleResRef } - allVerified, err := verifyResource(manifestYAMLs, kubeGetArgs, resBundleRef, sigResRef, keyPath, configPath, configField, configType, disableDefaultConfig, provenance, provResRef, certRef, certChain, rekorURL, oidcIssuer, outputFormat, concurrencyNum) + allVerified, err := verifyResource(manifestYAMLs, kubeGetArgs, resBundleRef, sigResRef, keyPath, configPath, configField, configType, disableDefaultConfig, provenance, allowInsecure, provResRef, certRef, certChain, rekorURL, oidcIssuer, outputFormat, concurrencyNum) if err != nil { log.Fatalf("error occurred during verify-resource: %s", err.Error()) } @@ -168,12 +169,13 @@ func NewCmdVerifyResource() *cobra.Command { cmd.PersistentFlags().StringVar(&certChain, "certificate-chain", "", "path to a list of CA certificates in PEM format which will be needed when building the certificate chain for the signing certificate. Must start with the parent intermediate CA certificate of the signing certificate and end with the root certificate") cmd.PersistentFlags().StringVar(&rekorURL, "rekor-url", "https://rekor.sigstore.dev", "URL of rekor STL server (default \"https://rekor.sigstore.dev\")") cmd.PersistentFlags().StringVar(&oidcIssuer, "oidc-issuer", "", "the OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth") + cmd.PersistentFlags().BoolVar(&allowInsecure, "allow-insecure-registry", false, "whether to allow insecure connections to registries. Don't use this for anything but testing") KOptions.ConfigFlags.AddFlags(cmd.PersistentFlags()) return cmd } -func verifyResource(yamls [][]byte, kubeGetArgs []string, resBundleRef, sigResRef, keyPath, configPath, configField, configType string, disableDefaultConfig, provenance bool, provResRef, certRef, certChain, rekorURL, oidcIssuer, outputFormat string, concurrencyNum int64) (bool, error) { +func verifyResource(yamls [][]byte, kubeGetArgs []string, resBundleRef, sigResRef, keyPath, configPath, configField, configType string, disableDefaultConfig, provenance, allowInsecure bool, provResRef, certRef, certChain, rekorURL, oidcIssuer, outputFormat string, concurrencyNum int64) (bool, error) { var err error start := time.Now().UTC() if outputFormat != "" { @@ -218,7 +220,7 @@ func verifyResource(yamls [][]byte, kubeGetArgs []string, resBundleRef, sigResRe } else if yamls != nil { objs, err = getObjsFromManifests(yamls, vo.IgnoreFields) } else if resBundleRef != "" { - manifestFetcher := k8smanifest.NewManifestFetcher(resBundleRef, "", vo.AnnotationConfig, nil, vo.MaxResourceManifestNum) + manifestFetcher := k8smanifest.NewManifestFetcher(resBundleRef, "", vo.AnnotationConfig, nil, vo.MaxResourceManifestNum, vo.AllowInsecure) imageManifestFetcher := manifestFetcher.(*k8smanifest.ImageManifestFetcher) var yamlsInImage [][]byte if yamlsInImage, err = imageManifestFetcher.FetchAll(); err == nil { @@ -244,6 +246,9 @@ func verifyResource(yamls [][]byte, kubeGetArgs []string, resBundleRef, sigResRe if provResRef != "" { vo.ProvenanceResourceRef = validateConfigMapRef(provResRef) } + if allowInsecure { + vo.AllowInsecure = true + } vo.Certificate = certRef vo.CertificateChain = certChain vo.RekorURL = rekorURL @@ -260,7 +265,7 @@ func verifyResource(yamls [][]byte, kubeGetArgs []string, resBundleRef, sigResRe img := imagesToBeused[i] // manifest fetch functions if img.imageType == k8smanifest.ArtifactManifestImage { - manifestFetcher := k8smanifest.NewManifestFetcher(img.ResourceBundleRef, "", vo.AnnotationConfig, nil, 0) + manifestFetcher := k8smanifest.NewManifestFetcher(img.ResourceBundleRef, "", vo.AnnotationConfig, nil, 0, vo.AllowInsecure) if fetcher, ok := manifestFetcher.(*k8smanifest.ImageManifestFetcher); ok { prepareFuncs = append(prepareFuncs, reflect.ValueOf(fetcher.FetchAll)) } @@ -288,7 +293,7 @@ func verifyResource(yamls [][]byte, kubeGetArgs []string, resBundleRef, sigResRe if vo.Provenance { // provenance functions - provGetter := k8smanifest.NewProvenanceGetter(nil, img.ResourceBundleRef, img.Digest, "") + provGetter := k8smanifest.NewProvenanceGetter(nil, img.ResourceBundleRef, img.Digest, "", allowInsecure) if getter, ok := provGetter.(*k8smanifest.ImageProvenanceGetter); ok { prepareFuncs = append(prepareFuncs, reflect.ValueOf(getter.Get)) } diff --git a/cmd/kubectl-sigstore/cli/verify_resource_test.go b/cmd/kubectl-sigstore/cli/verify_resource_test.go index a4681e8..1d9d518 100644 --- a/cmd/kubectl-sigstore/cli/verify_resource_test.go +++ b/cmd/kubectl-sigstore/cli/verify_resource_test.go @@ -207,7 +207,7 @@ var _ = Describe("Test Kubeutil Sigstore Functions", func() { return err } - verified, err := verifyResource(nil, []string{"cm", "sample-cm"}, "", "", "", "", "", "", false, false, "", "", "", "", "", "", 4) + verified, err := verifyResource(nil, []string{"cm", "sample-cm"}, "", "", "", "", "", "", false, false, false, "", "", "", "", "", "", 4) if err != nil { return err } @@ -227,7 +227,7 @@ var _ = Describe("Test Kubeutil Sigstore Functions", func() { } pubkeyPath := filepath.Join(testTempDir, "testpub") - verified, err := verifyResource(nil, []string{"cm", "sample-cm-signed"}, "", "", pubkeyPath, "", "", "", false, false, "", "", "", "", "", "json", 4) + verified, err := verifyResource(nil, []string{"cm", "sample-cm-signed"}, "", "", pubkeyPath, "", "", "", false, false, false, "", "", "", "", "", "json", 4) if err != nil { return err } diff --git a/docs/LATEST_RELEASE.md b/docs/LATEST_RELEASE.md index c630fb6..7fbeee1 100644 --- a/docs/LATEST_RELEASE.md +++ b/docs/LATEST_RELEASE.md @@ -1,3 +1,56 @@ + +# What's new in v0.4.1 + +In this release, we updated cosign version to v1.12.1 and added some CLI options as well as cosign. + +## Add `--allow-insecure-registry` for sign/verify commands + +Using cosign v1.12.0 and later, an insecure container registry must be accessed intentionally with `--allow-insecure-registry` option. + +We have added the same CLi option to sign/verify command in this project too. + +(Note: If you use images on ghcr.io, basically need to specify this option.) + +## Add `--force` for sign command + +Now cosign has `--force` option for sign command, and we have added it to this project too. + +If you want to skip some validations/checks using interactive CLI input while cosign signing, this option works for it. + +It is also used when the signing steps are automated and when you cannot input anything while the signing. + +--- + +# Backlog + +--- + +What's new in v0.4.0 + +In this release, a new signing method is added to `kubectl sigstore sign` command. It is not a default signing option yet, but we are planning the method will be default on the release v0.5.0 and later. The detail is described below. + +## Add a new signing method and the original signing method will be non-default soon + +The original signing method (`--tarball=yes`) creates a tarball of YAML files before signing. + +However, this may cause verification error when multiple signatures are provided. + +So we have added a new signing method (`--tarball=no`) that can solve this issue. + +The original method is still the default option now, but the new one will be default on v0.5.0 and later. + +## Support multiple signatures both for signing & verification + +A new signing option `--append-signature` (or `-A`) has been added for users to generate a signed YAML manifest that have multiple signatures. + +Users don't need to manually add them anymore. + +## Update cosign version to v1.10.1 + +We updated the version of cosign on which k8s-manifest-sigstore depends, and added some new command options to be consistent with cosign + +--- + # What's new in v0.3.0 In this release, we mainly updated verification functions so that users can easily & flexibly use `k8s-manifest-sigstore`. @@ -76,9 +129,7 @@ The following 2 options are added as verify-resource options to enable flexible The dependency version of cosign is updated to v1.8.0. ---- -# Backlog --- diff --git a/go.mod b/go.mod index 854a993..ecdd9e1 100644 --- a/go.mod +++ b/go.mod @@ -27,6 +27,7 @@ require ( github.com/spf13/cobra v1.5.0 github.com/tektoncd/chains v0.3.0 github.com/theupdateframework/go-tuf v0.5.0 + github.com/transparency-dev/merkle v0.0.1 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.23.5 @@ -234,7 +235,6 @@ require ( github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect github.com/tjfoc/gmsm v1.3.2 // indirect github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect - github.com/transparency-dev/merkle v0.0.1 // indirect github.com/urfave/cli v1.22.7 // indirect github.com/vbatts/tar-split v0.11.2 // indirect github.com/xanzy/go-gitlab v0.73.1 // indirect diff --git a/pkg/cosign/sign.go b/pkg/cosign/sign.go index 84fc95a..2046af6 100644 --- a/pkg/cosign/sign.go +++ b/pkg/cosign/sign.go @@ -39,19 +39,23 @@ import ( "github.com/sigstore/rekor/pkg/generated/client/entries" "github.com/sigstore/rekor/pkg/generated/models" log "github.com/sirupsen/logrus" + "github.com/transparency-dev/merkle/rfc6962" ) const ( - rekorServerEnvKey = "REKOR_SERVER" - defaultRekorServerURL = "https://rekor.sigstore.dev" - defaultOIDCIssuer = "https://oauth2.sigstore.dev/auth" - defaultOIDCClientID = "sigstore" - cosignPasswordEnvKey = "COSIGN_PASSWORD" - defaultTlogUploadTimeout = 10 - defaultKeylessTlogUploadTimeout = 90 // set to 90s for keyless as cosign recommends it in the help message + rekorServerEnvKey = "REKOR_SERVER" + defaultRekorServerURL = "https://rekor.sigstore.dev" + defaultOIDCIssuer = "https://oauth2.sigstore.dev/auth" + defaultOIDCClientID = "sigstore" + cosignPasswordEnvKey = "COSIGN_PASSWORD" + defaultTlogUploadTimeout = 90 // set to 90s for keyless as cosign recommends it in the help message ) -func SignImage(resBundleRef string, keyPath, certPath *string, rekorURL string, noTlogUpload bool, pf cosign.PassFunc, imageAnnotations map[string]interface{}) error { +const treeIDHexStringLen = 16 +const uuidHexStringLen = 64 +const entryIDHexStringLen = treeIDHexStringLen + uuidHexStringLen + +func SignImage(resBundleRef string, keyPath, certPath *string, rekorURL string, noTlogUpload, force bool, pf cosign.PassFunc, imageAnnotations map[string]interface{}, allowInsecure bool) error { // TODO: add support for sk (security key) and idToken (identity token for cert from fulcio) sk := false idToken := "" @@ -84,6 +88,9 @@ func SignImage(resBundleRef string, keyPath, certPath *string, rekorURL string, } regOpt := cliopt.RegistryOptions{} + if allowInsecure { + regOpt.AllowInsecure = true + } certPathStr := "" if certPath != nil { @@ -93,10 +100,10 @@ func SignImage(resBundleRef string, keyPath, certPath *string, rekorURL string, outputSignaturePath := "" outputCertificatePath := "" - return clisign.SignCmd(rootOpt, opt, regOpt, imageAnnotations, []string{resBundleRef}, certPathStr, "", true, outputSignaturePath, outputCertificatePath, "", false, false, "", noTlogUpload) + return clisign.SignCmd(rootOpt, opt, regOpt, imageAnnotations, []string{resBundleRef}, certPathStr, "", true, outputSignaturePath, outputCertificatePath, "", force, false, "", noTlogUpload) } -func SignBlob(blobPath string, keyPath, certPath *string, rekorURL string, noTlogUpload bool, pf cosign.PassFunc) (map[string][]byte, error) { +func SignBlob(blobPath string, keyPath, certPath *string, rekorURL string, noTlogUpload, force bool, pf cosign.PassFunc) (map[string][]byte, error) { // TODO: add support for sk (security key) and idToken (identity token for cert from fulcio) sk := false idToken := "" @@ -153,9 +160,6 @@ func SignBlob(blobPath string, keyPath, certPath *string, rekorURL string, noTlo m["message"] = base64Msg timeout := defaultTlogUploadTimeout - if keyPath == nil { - timeout = defaultKeylessTlogUploadTimeout - } rootOpt := &cliopt.RootOptions{Timeout: time.Duration(timeout) * time.Second} outputSignaturePath := "" outputCertificatePath := "" @@ -280,7 +284,7 @@ func GetRekorServerURL() string { return url } -// cosign has a bug in GetTlogEntry() function as of v1.9.0, so use this instead here +// cosign has a bug in GetTlogEntry() function as of v1.12.1, so use this instead here func GetTlogEntry(ctx context.Context, rekorClient *rekorgenclient.Rekor, uuid string) (*models.LogEntryAnon, error) { params := entries.NewGetLogEntryByUUIDParamsWithContext(ctx) params.SetEntryUUID(uuid) @@ -297,16 +301,44 @@ func GetTlogEntry(ctx context.Context, rekorClient *rekorgenclient.Rekor, uuid s return nil, errors.New("empty response") } -func verifyUUID(uuid string, e models.LogEntryAnon) error { - entryUUID, _ := hex.DecodeString(uuid) +func ComputeLeafHash(e *models.LogEntryAnon) ([]byte, error) { + entryBytes, err := base64.StdEncoding.DecodeString(e.Body.(string)) + if err != nil { + return nil, err + } + return rfc6962.DefaultHasher.HashLeaf(entryBytes), nil +} + +func getUUID(entryUUID string) (string, error) { + switch len(entryUUID) { + case uuidHexStringLen: + if _, err := hex.DecodeString(entryUUID); err != nil { + return "", fmt.Errorf("uuid %v is not a valid hex string: %w", entryUUID, err) + } + return entryUUID, nil + case entryIDHexStringLen: + uid := entryUUID[len(entryUUID)-uuidHexStringLen:] + return getUUID(uid) + default: + return "", fmt.Errorf("invalid ID len %v for %v", len(entryUUID), entryUUID) + } +} + +func verifyUUID(entryUUID string, e models.LogEntryAnon) error { + // Verify and get the UUID. + uid, err := getUUID(entryUUID) + if err != nil { + return err + } + uuid, _ := hex.DecodeString(uid) // Verify leaf hash matches hash of the entry body. - computedLeafHash, err := cosign.ComputeLeafHash(&e) + computedLeafHash, err := ComputeLeafHash(&e) if err != nil { return err } - if !bytes.Equal(computedLeafHash, entryUUID) { - return fmt.Errorf("computed leaf hash did not match entry UUID") + if !bytes.Equal(computedLeafHash, uuid) { + return fmt.Errorf("computed leaf hash did not match UUID") } return nil } diff --git a/pkg/cosign/sign_test.go b/pkg/cosign/sign_test.go index 13f2f0f..af1d546 100644 --- a/pkg/cosign/sign_test.go +++ b/pkg/cosign/sign_test.go @@ -60,7 +60,7 @@ func TestSignBlob(t *testing.T) { blobPath := files["blob"].fpath keyPath := files["key"].fpath - sigMap, err := SignBlob(blobPath, &keyPath, nil, "", false, passFuncForTest) + sigMap, err := SignBlob(blobPath, &keyPath, nil, "", false, false, passFuncForTest) if err != nil { t.Errorf("failed to load test files: %s", err.Error()) return diff --git a/pkg/cosign/verify.go b/pkg/cosign/verify.go index 2e1e96b..67a26a5 100644 --- a/pkg/cosign/verify.go +++ b/pkg/cosign/verify.go @@ -47,7 +47,7 @@ import ( "github.com/sigstore/sigstore/pkg/signature/payload" ) -func VerifyImage(resBundleRef, pubkeyPath, certRef, certChain, rekorURL, oidcIssuer string, rootCerts *x509.CertPool) (bool, string, *int64, error) { +func VerifyImage(resBundleRef, pubkeyPath, certRef, certChain, rekorURL, oidcIssuer string, rootCerts *x509.CertPool, allowInsecure bool) (bool, string, *int64, error) { ref, err := name.ParseReference(resBundleRef) if err != nil { return false, "", nil, fmt.Errorf("failed to parse image ref `%s`; %s", resBundleRef, err.Error()) @@ -61,6 +61,9 @@ func VerifyImage(resBundleRef, pubkeyPath, certRef, certChain, rekorURL, oidcIss } regOpt := &cliopt.RegistryOptions{} + if allowInsecure { + regOpt.AllowInsecure = true + } reqCliOpt, err := regOpt.ClientOpts(context.Background()) if err != nil { return false, "", nil, fmt.Errorf("failed to get registry client option; %s", err.Error()) diff --git a/pkg/k8smanifest/option.go b/pkg/k8smanifest/option.go index ede83ef..39877cf 100644 --- a/pkg/k8smanifest/option.go +++ b/pkg/k8smanifest/option.go @@ -131,8 +131,10 @@ type commonOption struct { // cosign sign option type cosignSignOption struct { - RekorURL string `json:"-"` - NoTlogUpload bool `json:"-"` + RekorURL string `json:"-"` + NoTlogUpload bool `json:"-"` + AllowInsecure bool `json:"-"` + Force bool `json:"-"` } // cosign verify option @@ -142,6 +144,7 @@ type cosignVerifyOption struct { RekorURL string `json:"-"` OIDCIssuer string `json:"-"` RootCerts *x509.CertPool `json:"-"` + AllowInsecure bool `json:"-"` } // annotation config for signing and verification diff --git a/pkg/k8smanifest/provenance.go b/pkg/k8smanifest/provenance.go index f87e9db..72bb6ce 100644 --- a/pkg/k8smanifest/provenance.go +++ b/pkg/k8smanifest/provenance.go @@ -96,16 +96,16 @@ type ProvenanceGetter interface { Get() ([]*Provenance, error) } -func NewProvenanceGetter(obj *unstructured.Unstructured, sigRef, imageHash, provResRef string) ProvenanceGetter { +func NewProvenanceGetter(obj *unstructured.Unstructured, sigRef, imageHash, provResRef string, allowInsecure bool) ProvenanceGetter { var resBundleRef string if !strings.HasPrefix(sigRef, kubeutil.InClusterObjectPrefix) { resBundleRef = sigRef } if obj != nil { - return &RecursiveImageProvenanceGetter{object: obj, manifestResourceBundleRef: resBundleRef, manifestProvenanceResourceRef: provResRef, cacheEnabled: true} + return &RecursiveImageProvenanceGetter{object: obj, manifestResourceBundleRef: resBundleRef, manifestProvenanceResourceRef: provResRef, cacheEnabled: true, allowInsecure: allowInsecure} } else if resBundleRef != "" && resBundleRef != SigRefEmbeddedInAnnotation { - return &ImageProvenanceGetter{resBundleRef: resBundleRef, imageHash: imageHash, cacheEnabled: true} + return &ImageProvenanceGetter{resBundleRef: resBundleRef, imageHash: imageHash, cacheEnabled: true, allowInsecure: allowInsecure} } else if provResRef != "" { return &ResourceProvenanceGetter{resourceRefString: provResRef} } else { @@ -118,6 +118,7 @@ type RecursiveImageProvenanceGetter struct { manifestResourceBundleRef string manifestProvenanceResourceRef string cacheEnabled bool + allowInsecure bool } func (g *RecursiveImageProvenanceGetter) Get() ([]*Provenance, error) { @@ -132,7 +133,7 @@ func (g *RecursiveImageProvenanceGetter) Get() ([]*Provenance, error) { return nil, errors.Wrap(err, "failed to get manifest image digest") } log.Trace("manifest image provenance getter") - imgGetter := NewProvenanceGetter(nil, g.manifestResourceBundleRef, digest, "") + imgGetter := NewProvenanceGetter(nil, g.manifestResourceBundleRef, digest, "", g.allowInsecure) prov, err := imgGetter.Get() if err != nil { return nil, errors.Wrap(err, "failed to get manifest image provenance") @@ -144,7 +145,7 @@ func (g *RecursiveImageProvenanceGetter) Get() ([]*Provenance, error) { } else if g.manifestProvenanceResourceRef != "" { // manifest prov from resource log.Trace("manifest resource provenance getter") - resGetter := NewProvenanceGetter(nil, "", "", g.manifestProvenanceResourceRef) + resGetter := NewProvenanceGetter(nil, "", "", g.manifestProvenanceResourceRef, g.allowInsecure) prov, err := resGetter.Get() if err != nil { return nil, errors.Wrap(err, "failed to get manifest resource provenance") @@ -167,7 +168,7 @@ func (g *RecursiveImageProvenanceGetter) Get() ([]*Provenance, error) { sumErr := []string{} for _, img := range images { log.Trace("object provenance getter for image:", img.ResourceBundleRef) - imgGetter := NewProvenanceGetter(nil, img.ResourceBundleRef, img.Digest, "") + imgGetter := NewProvenanceGetter(nil, img.ResourceBundleRef, img.Digest, "", g.allowInsecure) prov, err := imgGetter.Get() if err != nil { sumErr = append(sumErr, err.Error()) @@ -215,14 +216,14 @@ func (g *RecursiveImageProvenanceGetter) imageDigest(resBundleRef string) (strin } } else { log.Debug("image digest cache not found for image: ", resBundleRef) - digest, err = k8smnfutil.GetImageDigest(g.manifestResourceBundleRef) + digest, err = k8smnfutil.GetImageDigest(g.manifestResourceBundleRef, g.allowInsecure) err = k8smnfutil.SetCache(cacheKey, digest, err) if err != nil { log.Debug("failed to set image digest cache") } } } else { - digest, err = k8smnfutil.GetImageDigest(g.manifestResourceBundleRef) + digest, err = k8smnfutil.GetImageDigest(g.manifestResourceBundleRef, g.allowInsecure) } if err != nil { return "", errors.Wrap(err, "failed to get manifest image digest") @@ -231,9 +232,10 @@ func (g *RecursiveImageProvenanceGetter) imageDigest(resBundleRef string) (strin } type ImageProvenanceGetter struct { - resBundleRef string - imageHash string - cacheEnabled bool + resBundleRef string + imageHash string + cacheEnabled bool + allowInsecure bool } func (g *ImageProvenanceGetter) Get() ([]*Provenance, error) { @@ -378,6 +380,9 @@ func (g *ImageProvenanceGetter) getSBOMRef(resBundleRef string) (string, error) return "", err } regOpt := &cliopt.RegistryOptions{} + if g.allowInsecure { + regOpt.AllowInsecure = true + } reqCliOpt := regOpt.GetRegistryClientOpts(context.Background()) dstRef, err := cremote.SBOMTag(ref, cremote.WithRemoteOptions(reqCliOpt...)) if err != nil { diff --git a/pkg/k8smanifest/sign.go b/pkg/k8smanifest/sign.go index 97b45d8..f3c7d7e 100644 --- a/pkg/k8smanifest/sign.go +++ b/pkg/k8smanifest/sign.go @@ -28,13 +28,12 @@ import ( "github.com/pkg/errors" log "github.com/sirupsen/logrus" - "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/name" - "github.com/google/go-containerregistry/pkg/v1/remote" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" corev1client "k8s.io/client-go/kubernetes/typed/core/v1" + cliopt "github.com/sigstore/cosign/cmd/cosign/cli/options" "github.com/sigstore/cosign/pkg/cosign" cremote "github.com/sigstore/cosign/pkg/cosign/remote" k8scosign "github.com/sigstore/k8s-manifest-sigstore/pkg/cosign" @@ -76,8 +75,10 @@ func Sign(inputDir string, so *SignOption) ([]byte, error) { } cosignSignConfig := CosignSignConfig{ - RekorURL: so.RekorURL, - NoTlogUpload: so.NoTlogUpload, + RekorURL: so.RekorURL, + NoTlogUpload: so.NoTlogUpload, + AllowInsecure: so.AllowInsecure, + Force: so.Force, } signedBytes, err := NewSigner(so.ResourceBundleRef, so.KeyPath, so.CertPath, output, so.AppendSignature, so.ApplySigConfigMap, makeTarball, cosignSignConfig, so.AnnotationConfig, so.PassFunc).Sign(inputDir, output, so.ImageAnnotations) @@ -93,8 +94,10 @@ type Signer interface { } type CosignSignConfig struct { - RekorURL string - NoTlogUpload bool + RekorURL string + NoTlogUpload bool + AllowInsecure bool + Force bool } func NewSigner(resBundleRef, keyPath, certPath, output string, appendSig, doApply, tarball bool, cosignSignConfig CosignSignConfig, AnnotationConfig AnnotationConfig, pf cosign.PassFunc) Signer { @@ -167,12 +170,12 @@ func (s *ImageSigner) Sign(inputDir, output string, imageAnnotations map[string] } var signedBytes []byte // upload files as image - err = uploadFileToRegistry(inputDataBuffer.Bytes(), s.resBundleRef) + err = uploadFileToRegistry(inputDataBuffer.Bytes(), s.resBundleRef, s.AllowInsecure) if err != nil { return nil, errors.Wrap(err, "failed to upload image with manifest") } // sign the image - err = k8scosign.SignImage(s.resBundleRef, s.prikeyPath, s.certPath, s.RekorURL, s.NoTlogUpload, s.passFunc, imageAnnotations) + err = k8scosign.SignImage(s.resBundleRef, s.prikeyPath, s.certPath, s.RekorURL, s.NoTlogUpload, s.Force, s.passFunc, imageAnnotations, s.AllowInsecure) if err != nil { return nil, errors.Wrap(err, "failed to sign image") } @@ -235,7 +238,7 @@ func (s *BlobSigner) Sign(inputDir, output string, imageAnnotations map[string]i if err != nil { return nil, errors.Wrap(err, "failed to create a temporary blob file") } - sigMaps, err = k8scosign.SignBlob(tmpBlobFile, s.prikeyPath, s.certPath, s.RekorURL, s.NoTlogUpload, s.passFunc) + sigMaps, err = k8scosign.SignBlob(tmpBlobFile, s.prikeyPath, s.certPath, s.RekorURL, s.NoTlogUpload, s.Force, s.passFunc) if err != nil { return nil, errors.Wrap(err, "failed to sign a blob file") } @@ -288,7 +291,7 @@ func makeMessageYAML(inputDir string, outBuffer *bytes.Buffer, moList ...*k8ssig return nil } -func uploadFileToRegistry(inputData []byte, resBundleRef string) error { +func uploadFileToRegistry(inputData []byte, resBundleRef string, allowInsecure bool) error { dir, err := os.MkdirTemp("", "kubectl-sigstore-temp-dir") if err != nil { return err @@ -309,9 +312,12 @@ func uploadFileToRegistry(inputData []byte, resBundleRef string) error { } mediaTypeGetter := cremote.DefaultMediaTypeGetter - remoteAuthOption := remote.WithAuthFromKeychain(authn.DefaultKeychain) - remoteContextOption := remote.WithContext(context.Background()) - _, err = cremote.UploadFiles(ref, files, mediaTypeGetter, remoteAuthOption, remoteContextOption) + regOpt := cliopt.RegistryOptions{} + if allowInsecure { + regOpt.AllowInsecure = true + } + reqCliOpt := regOpt.GetRegistryClientOpts(context.Background()) + _, err = cremote.UploadFiles(ref, files, mediaTypeGetter, reqCliOpt...) if err != nil { return err } diff --git a/pkg/k8smanifest/verify.go b/pkg/k8smanifest/verify.go index efef15c..ba719ee 100644 --- a/pkg/k8smanifest/verify.go +++ b/pkg/k8smanifest/verify.go @@ -47,11 +47,12 @@ type verificationIdentity struct { } type CosignVerifyConfig struct { - CertRef string - CertChain string - RekorURL string - OIDCIssuer string - RootCerts *cryptox509.CertPool + CertRef string + CertChain string + RekorURL string + OIDCIssuer string + RootCerts *cryptox509.CertPool + AllowInsecure bool } func NewSignatureVerifier(objYAMLBytes []byte, sigRef string, pubkeyPath *string, signers []string, cosignVerifyConfig CosignVerifyConfig, annotationConfig AnnotationConfig) SignatureVerifier { @@ -151,7 +152,7 @@ func (v *ImageSignatureVerifier) Verify() (bool, string, *int64, error) { for i := range v.identityList { identity := v.identityList[i] // do normal image verification - verified, signerName, signedTimestamp, err = k8smnfcosign.VerifyImage(resBundleRef, identity.path, v.CertRef, v.CertChain, v.RekorURL, v.OIDCIssuer, v.RootCerts) + verified, signerName, signedTimestamp, err = k8smnfcosign.VerifyImage(resBundleRef, identity.path, v.CertRef, v.CertChain, v.RekorURL, v.OIDCIssuer, v.RootCerts, v.AllowInsecure) // cosign keyless returns signerName, so check if it matches the verificationIdentity if verified && identity.name != "" { @@ -339,9 +340,9 @@ type ManifestFetcher interface { // `resBundleRef` is used for judging if manifest is inside an image or not. // `annotationConfig` is used for annotation domain config like "cosign.sigstore.dev". // `ignoreFields` and `maxResourceManifestNum` are used inside manifest detection logic. -func NewManifestFetcher(resBundleRef, resourceRef string, annotationConfig AnnotationConfig, ignoreFields []string, maxResourceManifestNum int) ManifestFetcher { +func NewManifestFetcher(resBundleRef, resourceRef string, annotationConfig AnnotationConfig, ignoreFields []string, maxResourceManifestNum int, allowInsecure bool) ManifestFetcher { if resBundleRef != "" { - return &ImageManifestFetcher{resBundleRefString: resBundleRef, AnnotationConfig: annotationConfig, ignoreFields: ignoreFields, maxResourceManifestNum: maxResourceManifestNum, cacheEnabled: true} + return &ImageManifestFetcher{resBundleRefString: resBundleRef, AnnotationConfig: annotationConfig, ignoreFields: ignoreFields, maxResourceManifestNum: maxResourceManifestNum, cacheEnabled: true, allowInsecure: allowInsecure} } else { return &BlobManifestFetcher{AnnotationConfig: annotationConfig, resourceRefString: resourceRef, ignoreFields: ignoreFields, maxResourceManifestNum: maxResourceManifestNum} } @@ -354,6 +355,7 @@ type ImageManifestFetcher struct { ignoreFields []string // used by ManifestSearchByValue() maxResourceManifestNum int // used by ManifestSearchByValue() cacheEnabled bool + allowInsecure bool } func (f *ImageManifestFetcher) Fetch(objYAMLBytes []byte) ([][]byte, string, error) { @@ -432,7 +434,7 @@ func (f *ImageManifestFetcher) FetchAll() ([][]byte, error) { } func (f *ImageManifestFetcher) getConcatYAMLFromResourceBundleRef(resBundleRef string) ([]byte, error) { - image, err := k8smnfutil.PullImage(resBundleRef) + image, err := k8smnfutil.PullImage(resBundleRef, f.allowInsecure) if err != nil { return nil, err } diff --git a/pkg/k8smanifest/verify_manifest.go b/pkg/k8smanifest/verify_manifest.go index f8ff7d8..d7ab5be 100644 --- a/pkg/k8smanifest/verify_manifest.go +++ b/pkg/k8smanifest/verify_manifest.go @@ -61,7 +61,7 @@ func VerifyManifest(objManifest []byte, vo *VerifyManifestOption) (*VerifyResult var resourceManifests [][]byte var sigRef string - resourceManifests, sigRef, err = NewManifestFetcher(vo.ResourceBundleRef, vo.SignatureResourceRef, vo.AnnotationConfig, ignoreFields, vo.MaxResourceManifestNum).Fetch(objManifest) + resourceManifests, sigRef, err = NewManifestFetcher(vo.ResourceBundleRef, vo.SignatureResourceRef, vo.AnnotationConfig, ignoreFields, vo.MaxResourceManifestNum, vo.AllowInsecure).Fetch(objManifest) if err != nil { return nil, errors.Wrap(err, "reference YAML manifest not found for this manifest") } @@ -95,11 +95,12 @@ func VerifyManifest(objManifest []byte, vo *VerifyManifestOption) (*VerifyResult } cosignVerifyConfig := CosignVerifyConfig{ - CertRef: vo.Certificate, - CertChain: vo.CertificateChain, - RekorURL: vo.RekorURL, - OIDCIssuer: vo.OIDCIssuer, - RootCerts: vo.RootCerts, + CertRef: vo.Certificate, + CertChain: vo.CertificateChain, + RekorURL: vo.RekorURL, + OIDCIssuer: vo.OIDCIssuer, + RootCerts: vo.RootCerts, + AllowInsecure: vo.AllowInsecure, } sigVerified, signerName, _, err := NewSignatureVerifier(objManifest, sigRef, keyPath, signers, cosignVerifyConfig, vo.AnnotationConfig).Verify() diff --git a/pkg/k8smanifest/verify_resource.go b/pkg/k8smanifest/verify_resource.go index 6c1071d..3145205 100644 --- a/pkg/k8smanifest/verify_resource.go +++ b/pkg/k8smanifest/verify_resource.go @@ -97,7 +97,7 @@ func VerifyResource(obj unstructured.Unstructured, vo *VerifyResourceOption) (*V var resourceManifests [][]byte log.Debug("fetching manifest...") - resourceManifests, sigRef, err = NewManifestFetcher(vo.ResourceBundleRef, sigResourceRefString, vo.AnnotationConfig, ignoreFields, vo.MaxResourceManifestNum).Fetch(objBytes) + resourceManifests, sigRef, err = NewManifestFetcher(vo.ResourceBundleRef, sigResourceRefString, vo.AnnotationConfig, ignoreFields, vo.MaxResourceManifestNum, vo.AllowInsecure).Fetch(objBytes) if err != nil { retErr := errors.Wrap(err, "YAML manifest not found for this resource") log.Debugf("IsMessageNotFoundError(): %v", IsMessageNotFoundError(retErr)) @@ -133,11 +133,12 @@ func VerifyResource(obj unstructured.Unstructured, vo *VerifyResourceOption) (*V } cosignVerifyConfig := CosignVerifyConfig{ - CertRef: vo.Certificate, - CertChain: vo.CertificateChain, - RekorURL: vo.RekorURL, - OIDCIssuer: vo.OIDCIssuer, - RootCerts: vo.RootCerts, + CertRef: vo.Certificate, + CertChain: vo.CertificateChain, + RekorURL: vo.RekorURL, + OIDCIssuer: vo.OIDCIssuer, + RootCerts: vo.RootCerts, + AllowInsecure: vo.AllowInsecure, } var sigVerified bool @@ -156,7 +157,7 @@ func VerifyResource(obj unstructured.Unstructured, vo *VerifyResourceOption) (*V provenances := []*Provenance{} if vo.Provenance { - provenances, err = NewProvenanceGetter(&obj, sigRef, "", vo.ProvenanceResourceRef).Get() + provenances, err = NewProvenanceGetter(&obj, sigRef, "", vo.ProvenanceResourceRef, vo.AllowInsecure).Get() if err != nil { return nil, errors.Wrap(err, "failed to get provenance") } diff --git a/pkg/util/image.go b/pkg/util/image.go index 41b7267..99fe4e3 100644 --- a/pkg/util/image.go +++ b/pkg/util/image.go @@ -20,6 +20,7 @@ import ( "archive/tar" "bytes" "compress/gzip" + "context" "fmt" "io" "os" @@ -27,32 +28,42 @@ import ( "path/filepath" "strings" - "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/pkg/errors" + cliopt "github.com/sigstore/cosign/cmd/cosign/cli/options" "github.com/spf13/afero" ) -func PullImage(resBundleRef string) (v1.Image, error) { +func PullImage(resBundleRef string, allowInsecure bool) (v1.Image, error) { ref, err := name.ParseReference(resBundleRef) if err != nil { return nil, err } - img, err := remote.Image(ref, remote.WithAuthFromKeychain(authn.DefaultKeychain)) + regOpt := cliopt.RegistryOptions{} + if allowInsecure { + regOpt.AllowInsecure = true + } + reqCliOpt := regOpt.GetRegistryClientOpts(context.Background()) + img, err := remote.Image(ref, reqCliOpt...) if err != nil { return nil, err } return img, nil } -func GetImageDigest(resBundleRef string) (string, error) { +func GetImageDigest(resBundleRef string, allowInsecure bool) (string, error) { ref, err := name.ParseReference(resBundleRef) if err != nil { return "", err } - img, err := remote.Image(ref, remote.WithAuthFromKeychain(authn.DefaultKeychain)) + regOpt := cliopt.RegistryOptions{} + if allowInsecure { + regOpt.AllowInsecure = true + } + reqCliOpt := regOpt.GetRegistryClientOpts(context.Background()) + img, err := remote.Image(ref, reqCliOpt...) if err != nil { return "", err }