diff options
author | Miloslav Trmač <mitr@redhat.com> | 2022-07-29 00:08:40 +0200 |
---|---|---|
committer | Miloslav Trmač <mitr@redhat.com> | 2022-07-30 17:26:08 +0200 |
commit | d462da676cf0e97420d42ea64d72f69cab675922 (patch) | |
tree | 2dd71b580ec8f39b0672ea26f5b4ac2cdced8c8a /cmd | |
parent | 7075e2e1d5cc669e698070081a74093bdcbf115e (diff) | |
download | podman-d462da676cf0e97420d42ea64d72f69cab675922.tar.gz podman-d462da676cf0e97420d42ea64d72f69cab675922.tar.bz2 podman-d462da676cf0e97420d42ea64d72f69cab675922.zip |
Add support for creating sigstore signatures, and providing passphrases
- Allow creating sigstore signatures via --sign-by-sigstore-private-key .
Like existing --sign-by, it does not work remote (in this case
because we would have to copy the private key to the server).
- Allow passing a passphrase (which is mandatory for sigstore private keys)
via --sign-passphrase-file; if it is not provided, prompt interactively.
- Also, use that passphrase for --sign-by as well, allowing non-interactive
GPG use. (But --sign-passphrase-file can only be used with _one of_
--sign-by and --sign-by-sigstore-private-key.)
Note that unlike the existing code, (podman build) does not yet
implement sigstore (I'm not sure why it needs to, it seems not to
push images?) because Buildah does not expose the feature yet.
Also, (podman image sign) was not extended to support sigstore.
The test for this follows existing (podman image sign) tests
and doesn't work rootless; that could be improved by exposing
a registries.d override option.
The test for push is getting large; I didn't want to
start yet another registry container, but that would be an
alternative. In the future, Ginkgo's Ordered/BeforeAll
would allow starting a registry once and using it for two
tests.
Signed-off-by: Miloslav Trmač <mitr@redhat.com>
Diffstat (limited to 'cmd')
-rw-r--r-- | cmd/podman/common/sign.go | 36 | ||||
-rw-r--r-- | cmd/podman/images/push.go | 19 | ||||
-rw-r--r-- | cmd/podman/manifest/push.go | 19 |
3 files changed, 70 insertions, 4 deletions
diff --git a/cmd/podman/common/sign.go b/cmd/podman/common/sign.go new file mode 100644 index 000000000..e8a90ed57 --- /dev/null +++ b/cmd/podman/common/sign.go @@ -0,0 +1,36 @@ +package common + +import ( + "fmt" + + "github.com/containers/image/v5/pkg/cli" + "github.com/containers/podman/v4/pkg/domain/entities" + "github.com/containers/podman/v4/pkg/terminal" +) + +// PrepareSigningPassphrase updates pushOpts.SignPassphrase and SignSigstorePrivateKeyPassphrase based on a --sign-passphrase-file value signPassphraseFile, +// and validates pushOpts.Sign* consistency. +// It may interactively prompt for a passphrase if one is required and wasn’t provided otherwise. +func PrepareSigningPassphrase(pushOpts *entities.ImagePushOptions, signPassphraseFile string) error { + // c/common/libimage.Image does allow creating both simple signing and sigstore signatures simultaneously, + // with independent passphrases, but that would make the CLI probably too confusing. + // For now, use the passphrase with either, but only one of them. + if signPassphraseFile != "" && pushOpts.SignBy != "" && pushOpts.SignBySigstorePrivateKeyFile != "" { + return fmt.Errorf("only one of --sign-by and sign-by-sigstore-private-key can be used with --sign-passphrase-file") + } + + var passphrase string + if signPassphraseFile != "" { + p, err := cli.ReadPassphraseFile(signPassphraseFile) + if err != nil { + return err + } + passphrase = p + } else if pushOpts.SignBySigstorePrivateKeyFile != "" { + p := terminal.ReadPassphrase() + passphrase = string(p) + } // pushOpts.SignBy triggers a GPG-agent passphrase prompt, possibly using a more secure channel, so we usually shouldn’t prompt ourselves if no passphrase was explicitly provided. + pushOpts.SignPassphrase = passphrase + pushOpts.SignSigstorePrivateKeyPassphrase = []byte(passphrase) + return nil +} diff --git a/cmd/podman/images/push.go b/cmd/podman/images/push.go index 764936426..1734900de 100644 --- a/cmd/podman/images/push.go +++ b/cmd/podman/images/push.go @@ -17,8 +17,9 @@ import ( // CLI-only fields into the API types. type pushOptionsWrapper struct { entities.ImagePushOptions - TLSVerifyCLI bool // CLI only - CredentialsCLI string + TLSVerifyCLI bool // CLI only + CredentialsCLI string + SignPassphraseFileCLI string } var ( @@ -106,6 +107,14 @@ func pushFlags(cmd *cobra.Command) { flags.StringVar(&pushOptions.SignBy, signByFlagName, "", "Add a signature at the destination using the specified key") _ = cmd.RegisterFlagCompletionFunc(signByFlagName, completion.AutocompleteNone) + signBySigstorePrivateKeyFlagName := "sign-by-sigstore-private-key" + flags.StringVar(&pushOptions.SignBySigstorePrivateKeyFile, signBySigstorePrivateKeyFlagName, "", "Sign the image using a sigstore private key at `PATH`") + _ = cmd.RegisterFlagCompletionFunc(signBySigstorePrivateKeyFlagName, completion.AutocompleteDefault) + + signPassphraseFileFlagName := "sign-passphrase-file" + flags.StringVar(&pushOptions.SignPassphraseFileCLI, signPassphraseFileFlagName, "", "Read a passphrase for signing an image from `PATH`") + _ = cmd.RegisterFlagCompletionFunc(signPassphraseFileFlagName, completion.AutocompleteDefault) + flags.BoolVar(&pushOptions.TLSVerifyCLI, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") compressionFormat := "compression-format" @@ -118,6 +127,8 @@ func pushFlags(cmd *cobra.Command) { _ = flags.MarkHidden("digestfile") _ = flags.MarkHidden("quiet") _ = flags.MarkHidden(signByFlagName) + _ = flags.MarkHidden(signBySigstorePrivateKeyFlagName) + _ = flags.MarkHidden(signPassphraseFileFlagName) } if !registry.IsRemote() { flags.StringVar(&pushOptions.SignaturePolicy, "signature-policy", "", "Path to a signature-policy file") @@ -153,6 +164,10 @@ func imagePush(cmd *cobra.Command, args []string) error { pushOptions.Password = creds.Password } + if err := common.PrepareSigningPassphrase(&pushOptions.ImagePushOptions, pushOptions.SignPassphraseFileCLI); err != nil { + return err + } + // Let's do all the remaining Yoga in the API to prevent us from scattering // logic across (too) many parts of the code. return registry.ImageEngine().Push(registry.GetContext(), source, destination, pushOptions.ImagePushOptions) diff --git a/cmd/podman/manifest/push.go b/cmd/podman/manifest/push.go index df394d275..6afd8ea4c 100644 --- a/cmd/podman/manifest/push.go +++ b/cmd/podman/manifest/push.go @@ -20,8 +20,9 @@ import ( type manifestPushOptsWrapper struct { entities.ImagePushOptions - TLSVerifyCLI bool // CLI only - CredentialsCLI string + TLSVerifyCLI bool // CLI only + CredentialsCLI string + SignPassphraseFileCLI string } var ( @@ -72,6 +73,14 @@ func init() { flags.StringVar(&manifestPushOpts.SignBy, signByFlagName, "", "sign the image using a GPG key with the specified `FINGERPRINT`") _ = pushCmd.RegisterFlagCompletionFunc(signByFlagName, completion.AutocompleteNone) + signBySigstorePrivateKeyFlagName := "sign-by-sigstore-private-key" + flags.StringVar(&manifestPushOpts.SignBySigstorePrivateKeyFile, signBySigstorePrivateKeyFlagName, "", "Sign the image using a sigstore private key at `PATH`") + _ = pushCmd.RegisterFlagCompletionFunc(signBySigstorePrivateKeyFlagName, completion.AutocompleteDefault) + + signPassphraseFileFlagName := "sign-passphrase-file" + flags.StringVar(&manifestPushOpts.SignPassphraseFileCLI, signPassphraseFileFlagName, "", "Read a passphrase for signing an image from `PATH`") + _ = pushCmd.RegisterFlagCompletionFunc(signPassphraseFileFlagName, completion.AutocompleteDefault) + flags.BoolVar(&manifestPushOpts.TLSVerifyCLI, "tls-verify", true, "require HTTPS and verify certificates when accessing the registry") flags.BoolVarP(&manifestPushOpts.Quiet, "quiet", "q", false, "don't output progress information when pushing lists") flags.SetNormalizeFunc(utils.AliasFlags) @@ -79,6 +88,8 @@ func init() { if registry.IsRemote() { _ = flags.MarkHidden("cert-dir") _ = flags.MarkHidden(signByFlagName) + _ = flags.MarkHidden(signBySigstorePrivateKeyFlagName) + _ = flags.MarkHidden(signPassphraseFileFlagName) } } @@ -104,6 +115,10 @@ func push(cmd *cobra.Command, args []string) error { manifestPushOpts.Password = creds.Password } + if err := common.PrepareSigningPassphrase(&manifestPushOpts.ImagePushOptions, manifestPushOpts.SignPassphraseFileCLI); err != nil { + return err + } + // TLS verification in c/image is controlled via a `types.OptionalBool` // which allows for distinguishing among set-true, set-false, unspecified // which is important to implement a sane way of dealing with defaults of |