mirror of
https://github.com/docker/cli.git
synced 2026-01-26 15:41:42 +03:00
move the `trust` subcommands to a plugin, so that the subcommands can
be installed separate from the `docker trust` integration in push/pull
(for situations where trust verification happens on the daemon side).
make binary
go build -o /usr/libexec/docker/cli-plugins/docker-trust ./cmd/docker-trust
docker info
Client:
Version: 28.2.0-dev
Context: default
Debug Mode: false
Plugins:
buildx: Docker Buildx (Docker Inc.)
Version: v0.24.0
Path: /usr/libexec/docker/cli-plugins/docker-buildx
trust: Manage trust on Docker images (Docker Inc.)
Version: unknown-version
Path: /usr/libexec/docker/cli-plugins/docker-trust
docker trust --help
Usage: docker trust [OPTIONS] COMMAND
Extended build capabilities with BuildKit
Options:
-D, --debug Enable debug logging
Management Commands:
key Manage keys for signing Docker images
signer Manage entities who can sign Docker images
Commands:
inspect Return low-level information about keys and signatures
revoke Remove trust for an image
sign Sign an image
Run 'docker trust COMMAND --help' for more information on a command.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
133 lines
4.3 KiB
Go
133 lines
4.3 KiB
Go
package trust
|
|
|
|
import (
|
|
"encoding/pem"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/docker/cli/cli"
|
|
"github.com/docker/cli/cli/command"
|
|
"github.com/docker/cli/cmd/docker-trust/internal/trust"
|
|
"github.com/docker/cli/internal/lazyregexp"
|
|
"github.com/spf13/cobra"
|
|
"github.com/theupdateframework/notary"
|
|
"github.com/theupdateframework/notary/trustmanager"
|
|
"github.com/theupdateframework/notary/tuf/data"
|
|
tufutils "github.com/theupdateframework/notary/tuf/utils"
|
|
)
|
|
|
|
type keyGenerateOptions struct {
|
|
name string
|
|
directory string
|
|
}
|
|
|
|
func newKeyGenerateCommand(dockerCLI command.Streams) *cobra.Command {
|
|
options := keyGenerateOptions{}
|
|
cmd := &cobra.Command{
|
|
Use: "generate NAME",
|
|
Short: "Generate and load a signing key-pair",
|
|
Args: cli.ExactArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
options.name = args[0]
|
|
return setupPassphraseAndGenerateKeys(dockerCLI, options)
|
|
},
|
|
DisableFlagsInUseLine: true,
|
|
}
|
|
flags := cmd.Flags()
|
|
flags.StringVar(&options.directory, "dir", "", "Directory to generate key in, defaults to current directory")
|
|
return cmd
|
|
}
|
|
|
|
// key names can use lowercase alphanumeric + _ + - characters
|
|
var validKeyName = lazyregexp.New(`^[a-z0-9][a-z0-9\_\-]*$`).MatchString
|
|
|
|
// validate that all of the key names are unique and are alphanumeric + _ + -
|
|
// and that we do not already have public key files in the target dir on disk
|
|
func validateKeyArgs(keyName string, targetDir string) error {
|
|
if !validKeyName(keyName) {
|
|
return fmt.Errorf("key name \"%s\" must start with lowercase alphanumeric characters and can include \"-\" or \"_\" after the first character", keyName)
|
|
}
|
|
|
|
pubKeyFileName := keyName + ".pub"
|
|
if _, err := os.Stat(targetDir); err != nil {
|
|
return fmt.Errorf("public key path does not exist: \"%s\"", targetDir)
|
|
}
|
|
targetPath := filepath.Join(targetDir, pubKeyFileName)
|
|
if _, err := os.Stat(targetPath); err == nil {
|
|
return fmt.Errorf("public key file already exists: \"%s\"", targetPath)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func setupPassphraseAndGenerateKeys(streams command.Streams, opts keyGenerateOptions) error {
|
|
targetDir := opts.directory
|
|
if targetDir == "" {
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
targetDir = cwd
|
|
}
|
|
return validateAndGenerateKey(streams, opts.name, targetDir)
|
|
}
|
|
|
|
func validateAndGenerateKey(streams command.Streams, keyName string, workingDir string) error {
|
|
freshPassRetGetter := func() notary.PassRetriever { return trust.GetPassphraseRetriever(streams.In(), streams.Out()) }
|
|
if err := validateKeyArgs(keyName, workingDir); err != nil {
|
|
return err
|
|
}
|
|
_, _ = fmt.Fprintf(streams.Out(), "Generating key for %s...\n", keyName)
|
|
// Automatically load the private key to local storage for use
|
|
privKeyFileStore, err := trustmanager.NewKeyFileStore(trust.GetTrustDirectory(), freshPassRetGetter())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
pubPEM, err := generateKeyAndOutputPubPEM(keyName, privKeyFileStore)
|
|
if err != nil {
|
|
_, _ = fmt.Fprint(streams.Out(), err)
|
|
return fmt.Errorf("failed to generate key for %s: %w", keyName, err)
|
|
}
|
|
|
|
// Output the public key to a file in the CWD or specified dir
|
|
writtenPubFile, err := writePubKeyPEMToDir(pubPEM, keyName, workingDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, _ = fmt.Fprintln(streams.Out(), "Successfully generated and loaded private key. Corresponding public key available:", writtenPubFile)
|
|
|
|
return nil
|
|
}
|
|
|
|
func generateKeyAndOutputPubPEM(keyName string, privKeyStore trustmanager.KeyStore) (pem.Block, error) {
|
|
privKey, err := tufutils.GenerateKey(data.ECDSAKey)
|
|
if err != nil {
|
|
return pem.Block{}, err
|
|
}
|
|
|
|
if err := privKeyStore.AddKey(trustmanager.KeyInfo{Role: data.RoleName(keyName)}, privKey); err != nil {
|
|
return pem.Block{}, err
|
|
}
|
|
|
|
pubKey := data.PublicKeyFromPrivate(privKey)
|
|
return pem.Block{
|
|
Type: "PUBLIC KEY",
|
|
Headers: map[string]string{
|
|
"role": keyName,
|
|
},
|
|
Bytes: pubKey.Public(),
|
|
}, nil
|
|
}
|
|
|
|
func writePubKeyPEMToDir(pubPEM pem.Block, keyName, workingDir string) (string, error) {
|
|
// Output the public key to a file in the CWD or specified dir
|
|
pubFileName := strings.Join([]string{keyName, "pub"}, ".")
|
|
pubFilePath := filepath.Join(workingDir, pubFileName)
|
|
if err := os.WriteFile(pubFilePath, pem.EncodeToMemory(&pubPEM), notary.PrivNoExecPerms); err != nil {
|
|
return "", fmt.Errorf("failed to write public key to %s: %w", pubFilePath, err)
|
|
}
|
|
return pubFilePath, nil
|
|
}
|