-
Notifications
You must be signed in to change notification settings - Fork 790
Require new bundle format for signing and verification #4959
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 9 commits
28ccd1f
d6d0a2f
a2efab0
2a70df6
e3775b5
baa6db0
4f33850
4537ba6
de506c6
d435b3b
5ac6312
6c0cc72
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,7 +31,7 @@ func Attest() *cobra.Command { | |
| cmd := &cobra.Command{ | ||
| Use: "attest", | ||
| Short: "Attest the supplied container image", | ||
| Example: ` cosign attest --key <key path>|<kms uri> [--predicate <path>] [--a key=value] [--no-upload=true|false] [--record-creation-timestamp=true|false] [--f] [--r] <image uri> | ||
| Example: ` cosign attest --key <key path>|<kms uri> [--predicate <path>] [--no-upload=true|false] [--yes] <image uri> | ||
|
|
||
| # attach an attestation to a container image Google sign-in | ||
| cosign attest --timeout 90s --predicate <FILE> --type <TYPE> <IMAGE> | ||
|
|
@@ -61,16 +61,24 @@ func Attest() *cobra.Command { | |
| echo <PAYLOAD> | cosign attest --predicate - <IMAGE> | ||
|
|
||
| # write attestation to stdout | ||
| cosign attest --predicate <FILE> --type <TYPE> --key cosign.key --no-upload true <IMAGE> | ||
|
|
||
| # attach an attestation to a container image and honor the creation timestamp of the signature | ||
| cosign attest --predicate <FILE> --type <TYPE> --key cosign.key --record-creation-timestamp <IMAGE>`, | ||
| cosign attest --predicate <FILE> --type <TYPE> --key cosign.key --no-upload true <IMAGE>`, | ||
|
|
||
| Args: cobra.MinimumNArgs(1), | ||
| PersistentPreRun: options.BindViper, | ||
| PreRunE: func(_ *cobra.Command, _ []string) error { | ||
| if o.NewBundleFormat && o.NoUpload && o.BundlePath == "" { | ||
| return fmt.Errorf("must enable upload to the OCI registry or specify a local --bundle path with --new-bundle-format") | ||
| if o.NoUpload && o.BundlePath == "" { | ||
| return fmt.Errorf("must enable upload to the OCI registry or specify a local --bundle path") | ||
| } | ||
| var attestType string | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here, what's |
||
| if o.Key == "" && !o.SecurityKey.Use { | ||
| attestType = "keyless" | ||
| } else if o.IssueCertificate { | ||
| attestType = "certificate-based" | ||
| } | ||
| if attestType != "" { | ||
| if !o.UseSigningConfig && o.SigningConfigPath == "" { | ||
| return fmt.Errorf("%s attesting requires a signing config (either from TUF via --use-signing-config or explicitly via a file with --signing-config)", attestType) | ||
| } | ||
| } | ||
| return nil | ||
| }, | ||
|
|
@@ -85,46 +93,35 @@ func Attest() *cobra.Command { | |
| PassFunc: generate.GetPass, | ||
| Sk: o.SecurityKey.Use, | ||
| Slot: o.SecurityKey.Slot, | ||
| FulcioURL: o.Fulcio.URL, | ||
| IDToken: o.Fulcio.IdentityToken, | ||
| FulcioAuthFlow: o.Fulcio.AuthFlow, | ||
| InsecureSkipFulcioVerify: o.Fulcio.InsecureSkipFulcioVerify, | ||
| RekorURL: o.Rekor.URL, | ||
| OIDCIssuer: o.OIDC.Issuer, | ||
| OIDCClientID: o.OIDC.ClientID, | ||
| OIDCClientSecret: oidcClientSecret, | ||
| OIDCRedirectURL: o.OIDC.RedirectURL, | ||
| OIDCDisableProviders: o.OIDC.DisableAmbientProviders, | ||
| OIDCProvider: o.OIDC.Provider, | ||
| SkipConfirmation: o.SkipConfirmation, | ||
| TSAClientCACert: o.TSAClientCACert, | ||
| TSAClientKey: o.TSAClientKey, | ||
| TSAClientCert: o.TSAClientCert, | ||
| TSAServerName: o.TSAServerName, | ||
| TSAServerURL: o.TSAServerURL, | ||
| IssueCertificateForExistingKey: o.IssueCertificate, | ||
| BundlePath: o.BundlePath, | ||
| NewBundleFormat: o.NewBundleFormat, | ||
| } | ||
| if err := signcommon.LoadTrustedMaterialAndSigningConfig(cmd.Context(), &ko, o.UseSigningConfig, o.SigningConfigPath, | ||
| o.Rekor.URL, o.Fulcio.URL, o.OIDC.Issuer, o.TSAServerURL, o.TrustedRootPath, o.TlogUpload, | ||
| o.NewBundleFormat, "", o.Key, o.IssueCertificate, | ||
| "", "", "", "", "", ""); err != nil { | ||
| o.TrustedRootPath, o.Key); err != nil { | ||
| return err | ||
| } | ||
|
|
||
| attestCommand := attest.AttestCommand{ | ||
| KeyOpts: ko, | ||
| RegistryOptions: o.Registry, | ||
| CertPath: o.Cert, | ||
| CertChainPath: o.CertChain, | ||
| NoUpload: o.NoUpload, | ||
| PredicatePath: o.Predicate.Path, | ||
| PredicateType: o.Predicate.Type, | ||
| Replace: o.Replace, | ||
| Timeout: ro.Timeout, | ||
| TlogUpload: o.TlogUpload, | ||
| RekorEntryType: o.RekorEntryType, | ||
| RecordCreationTimestamp: o.RecordCreationTimestamp, | ||
| KeyOpts: ko, | ||
| RegistryOptions: o.Registry, | ||
| CertPath: o.Cert, | ||
| CertChainPath: o.CertChain, | ||
| NoUpload: o.NoUpload, | ||
| PredicatePath: o.Predicate.Path, | ||
| PredicateType: o.Predicate.Type, | ||
| Timeout: ro.Timeout, | ||
| } | ||
|
|
||
| for _, img := range args { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,37 +24,24 @@ import ( | |
|
|
||
| "github.com/google/go-containerregistry/pkg/name" | ||
| v1 "github.com/google/go-containerregistry/pkg/v1" | ||
| "google.golang.org/protobuf/encoding/protojson" | ||
|
|
||
| "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" | ||
| "github.com/sigstore/cosign/v3/cmd/cosign/cli/signcommon" | ||
| "github.com/sigstore/cosign/v3/internal/ui" | ||
| "github.com/sigstore/cosign/v3/pkg/cosign/attestation" | ||
| cbundle "github.com/sigstore/cosign/v3/pkg/cosign/bundle" | ||
| cremote "github.com/sigstore/cosign/v3/pkg/cosign/remote" | ||
| "github.com/sigstore/cosign/v3/pkg/oci/mutate" | ||
| ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote" | ||
| "github.com/sigstore/cosign/v3/pkg/oci/static" | ||
| "github.com/sigstore/cosign/v3/pkg/types" | ||
| protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1" | ||
| "github.com/sigstore/sigstore/pkg/signature" | ||
| ) | ||
|
|
||
| // nolint | ||
| type AttestCommand struct { | ||
| options.KeyOpts | ||
| options.RegistryOptions | ||
| CertPath string | ||
| CertChainPath string | ||
| NoUpload bool | ||
| PredicatePath string | ||
| PredicateType string | ||
| Replace bool | ||
| Timeout time.Duration | ||
| TlogUpload bool | ||
| TSAServerURL string | ||
| RekorEntryType string | ||
| RecordCreationTimestamp bool | ||
| CertPath string | ||
| CertChainPath string | ||
| NoUpload bool | ||
| PredicatePath string | ||
| PredicateType string | ||
| Timeout time.Duration | ||
| } | ||
|
|
||
| // nolint | ||
|
|
@@ -68,10 +55,6 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error { | |
| return fmt.Errorf("predicate cannot be empty") | ||
| } | ||
|
|
||
| if c.RekorEntryType != "dsse" && c.RekorEntryType != "intoto" { | ||
| return fmt.Errorf("unknown value for rekor-entry-type") | ||
| } | ||
|
|
||
| predicateURI, err := options.ParsePredicateType(c.PredicateType) | ||
| if err != nil { | ||
| return err | ||
|
|
@@ -135,115 +118,34 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error { | |
| } | ||
|
|
||
| if c.SigningConfig == nil { | ||
| c.SigningConfig, err = signcommon.NewSigningConfigFromKeyOpts(c.KeyOpts, c.TlogUpload) | ||
| if err != nil { | ||
| return fmt.Errorf("creating signing config: %w", err) | ||
| } | ||
| c.SigningConfig = signcommon.NewEmptySigningConfig() | ||
| } | ||
|
|
||
| bundleBytes, pubKey, hashAlgProto, err := signcommon.NewAttestationBundle(ctx, c.KeyOpts, c.CertPath, c.CertChainPath, bundleOpts, c.SigningConfig, c.TrustedMaterial) | ||
| shouldUpload, err := signcommon.ShouldUploadToTlog(ctx, c.KeyOpts, digest, len(c.SigningConfig.RekorLogURLs()) > 0) | ||
| if err != nil { | ||
| return fmt.Errorf("creating bundle: %w", err) | ||
| } | ||
|
|
||
| if c.NewBundleFormat { | ||
| if c.BundlePath != "" { | ||
| if err := os.WriteFile(c.BundlePath, bundleBytes, 0600); err != nil { | ||
| return fmt.Errorf("create bundle file: %w", err) | ||
| } | ||
| ui.Infof(ctx, "Wrote bundle to file %s", c.BundlePath) | ||
| } | ||
|
|
||
| if !c.NoUpload { | ||
| if err := ociremote.WriteAttestationNewBundleFormat(digest, bundleBytes, bundleOpts.PredicateType, ociremoteOpts...); err != nil { | ||
| return fmt.Errorf("writing bundle: %w", err) | ||
| } | ||
| } | ||
| return nil | ||
| return fmt.Errorf("should upload to tlog: %w", err) | ||
| } | ||
|
|
||
| var pb protobundle.Bundle | ||
| if err := protojson.Unmarshal(bundleBytes, &pb); err != nil { | ||
| return fmt.Errorf("unmarshalling bundle: %w", err) | ||
| if !shouldUpload { | ||
| c.SigningConfig.WithRekorLogURLs() | ||
| } | ||
|
|
||
| bundleComponents, err := signcommon.ExtractComponentsFromProtoBundle(&pb) | ||
| bundleBytes, err := signcommon.NewAttestationBundle(ctx, c.KeyOpts, c.CertPath, c.CertChainPath, bundleOpts, c.SigningConfig, c.TrustedMaterial) | ||
| if err != nil { | ||
| return fmt.Errorf("extracting components from bundle: %w", err) | ||
| } | ||
|
|
||
| legacyBundleBytes, err := signcommon.NewLegacyBundleFromProtoBundleComponents(bundleComponents) | ||
| if err != nil { | ||
| return fmt.Errorf("creating legacy bundle: %w", err) | ||
| return fmt.Errorf("creating bundle: %w", err) | ||
| } | ||
|
|
||
| if c.BundlePath != "" { | ||
| if err := os.WriteFile(c.BundlePath, legacyBundleBytes, 0600); err != nil { | ||
| if err := os.WriteFile(c.BundlePath, bundleBytes, 0600); err != nil { | ||
| return fmt.Errorf("create bundle file: %w", err) | ||
| } | ||
| ui.Infof(ctx, "Wrote bundle to file %s", c.BundlePath) | ||
| } | ||
|
|
||
| if c.NoUpload { | ||
| return nil | ||
| } | ||
|
|
||
| certPem, chainPem := signcommon.EncodeCertificatesToPEM(bundleComponents.Certificates) | ||
|
|
||
| opts := []static.Option{ | ||
| static.WithLayerMediaType(types.DssePayloadType), | ||
| static.WithAnnotations(map[string]string{ | ||
| "predicateType": predicateURI, | ||
| }), | ||
| } | ||
| if certPem != nil { | ||
| opts = append(opts, static.WithCertChain(certPem, chainPem)) | ||
| } | ||
|
|
||
| if len(bundleComponents.RFC3161Timestamps) > 0 { | ||
| opts = append(opts, static.WithRFC3161Timestamp(cbundle.TimestampToRFC3161Timestamp(bundleComponents.RFC3161Timestamps[0].GetSignedTimestamp()))) | ||
| } | ||
|
|
||
| predicateTypeAnnotation := map[string]string{ | ||
| "predicateType": predicateURI, | ||
| } | ||
| // Add predicateType as manifest annotation | ||
| opts = append(opts, static.WithAnnotations(predicateTypeAnnotation)) | ||
|
|
||
| if len(bundleComponents.RekorEntries) > 0 { | ||
| opts = append(opts, static.WithBundle(signcommon.RekorBundleFromProtoTlogEntry(bundleComponents.RekorEntries[0]))) | ||
| } | ||
|
|
||
| ociSig, err := static.NewAttestation(bundleComponents.Signature, opts...) | ||
| if err != nil { | ||
| return fmt.Errorf("creating attestation: %w", err) | ||
| } | ||
|
|
||
| // We don't actually need to access the remote entity to attach things to it | ||
| // so we use a placeholder here. | ||
| se := ociremote.SignedUnknown(digest, ociremoteOpts...) | ||
|
|
||
| ddVerifier, err := signature.LoadVerifier(pubKey, signcommon.ProtoHashAlgoToHash(hashAlgProto)) | ||
| if err != nil { | ||
| return fmt.Errorf("loading verifier: %w", err) | ||
| } | ||
| dd := cremote.NewDupeDetector(ddVerifier) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be outdated at this point, but I just want to explicitly confirm that there's no reason to keep this around. Duplications aren't relevant because we're not attaching a signature multiple times to an annotation. I'm not certain why record creation timestamp was added though, that's the one that gives me pause. cc @steiza |
||
| signOpts := []mutate.SignOption{ | ||
| mutate.WithDupeDetector(dd), | ||
| mutate.WithRecordCreationTimestamp(c.RecordCreationTimestamp), | ||
| } | ||
|
|
||
| if c.Replace { | ||
| ro := cremote.NewReplaceOp(predicateURI) | ||
| signOpts = append(signOpts, mutate.WithReplaceOp(ro)) | ||
| } | ||
|
|
||
| // Attach the attestation to the entity. | ||
| newSE, err := mutate.AttachAttestationToEntity(se, ociSig, signOpts...) | ||
| if err != nil { | ||
| return fmt.Errorf("attaching attestation: %w", err) | ||
| if !c.NoUpload { | ||
| if err := ociremote.WriteAttestationNewBundleFormat(digest, bundleBytes, bundleOpts.PredicateType, ociremoteOpts...); err != nil { | ||
| return fmt.Errorf("writing bundle: %w", err) | ||
| } | ||
| } | ||
|
|
||
| // Publish the attestations associated with this entity | ||
| return ociremote.WriteAttestations(digest.Repository, newSE, ociremoteOpts...) | ||
| return nil | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
--key should be optional