mirror of
https://github.com/docker/cli.git
synced 2026-01-28 04:20:55 +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>
151 lines
4.7 KiB
Go
151 lines
4.7 KiB
Go
package trust
|
|
|
|
import (
|
|
"context"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"sort"
|
|
|
|
"github.com/distribution/reference"
|
|
"github.com/docker/cli/cli/streams"
|
|
"github.com/docker/cli/internal/jsonstream"
|
|
registrytypes "github.com/moby/moby/api/types/registry"
|
|
"github.com/opencontainers/go-digest"
|
|
"github.com/theupdateframework/notary/client"
|
|
"github.com/theupdateframework/notary/tuf/data"
|
|
)
|
|
|
|
// Streams is an interface which exposes the standard input and output streams.
|
|
//
|
|
// Same interface as [github.com/docker/cli/cli/command.Streams] but defined here to prevent a circular import.
|
|
type Streams interface {
|
|
In() *streams.In
|
|
Out() *streams.Out
|
|
Err() *streams.Out
|
|
}
|
|
|
|
// PushResult contains the tag, manifest digest, and manifest size from the
|
|
// push. It's used to signal this information to the trust code in the client
|
|
// so it can sign the manifest if necessary.
|
|
type PushResult struct {
|
|
Tag string
|
|
Digest string
|
|
Size int
|
|
}
|
|
|
|
// PushTrustedReference pushes a canonical reference to the trust server.
|
|
//
|
|
//nolint:gocyclo
|
|
func PushTrustedReference(ctx context.Context, ioStreams Streams, repoInfo *RepositoryInfo, ref reference.Named, authConfig registrytypes.AuthConfig, in io.Reader, userAgent string) error {
|
|
// If it is a trusted push we would like to find the target entry which match the
|
|
// tag provided in the function and then do an AddTarget later.
|
|
notaryTarget := &client.Target{}
|
|
// Count the times of calling for handleTarget,
|
|
// if it is called more that once, that should be considered an error in a trusted push.
|
|
cnt := 0
|
|
handleTarget := func(msg jsonstream.JSONMessage) {
|
|
cnt++
|
|
if cnt > 1 {
|
|
// handleTarget should only be called once. This will be treated as an error.
|
|
return
|
|
}
|
|
|
|
var pushResult PushResult
|
|
err := json.Unmarshal(*msg.Aux, &pushResult)
|
|
if err == nil && pushResult.Tag != "" {
|
|
if dgst, err := digest.Parse(pushResult.Digest); err == nil {
|
|
h, err := hex.DecodeString(dgst.Hex())
|
|
if err != nil {
|
|
notaryTarget = nil
|
|
return
|
|
}
|
|
notaryTarget.Name = pushResult.Tag
|
|
notaryTarget.Hashes = data.Hashes{string(dgst.Algorithm()): h}
|
|
notaryTarget.Length = int64(pushResult.Size)
|
|
}
|
|
}
|
|
}
|
|
|
|
var tag string
|
|
switch x := ref.(type) {
|
|
case reference.Digested:
|
|
return errors.New("cannot push a digest reference")
|
|
case reference.Tagged:
|
|
tag = x.Tag()
|
|
default:
|
|
// We want trust signatures to always take an explicit tag,
|
|
// otherwise it will act as an untrusted push.
|
|
if err := jsonstream.Display(ctx, in, ioStreams.Out()); err != nil {
|
|
return err
|
|
}
|
|
_, _ = fmt.Fprintln(ioStreams.Err(), "No tag specified, skipping trust metadata push")
|
|
return nil
|
|
}
|
|
|
|
if err := jsonstream.Display(ctx, in, ioStreams.Out(), jsonstream.WithAuxCallback(handleTarget)); err != nil {
|
|
return err
|
|
}
|
|
|
|
if cnt > 1 {
|
|
return errors.New("internal error: only one call to handleTarget expected")
|
|
}
|
|
|
|
if notaryTarget == nil {
|
|
return errors.New("no targets found, provide a specific tag in order to sign it")
|
|
}
|
|
|
|
_, _ = fmt.Fprintln(ioStreams.Out(), "Signing and pushing trust metadata")
|
|
|
|
repo, err := GetNotaryRepository(ioStreams.In(), ioStreams.Out(), userAgent, repoInfo, &authConfig, "push", "pull")
|
|
if err != nil {
|
|
return fmt.Errorf("error establishing connection to trust repository: %w", err)
|
|
}
|
|
|
|
// get the latest repository metadata so we can figure out which roles to sign
|
|
_, err = repo.ListTargets()
|
|
|
|
switch err.(type) {
|
|
case client.ErrRepoNotInitialized, client.ErrRepositoryNotExist:
|
|
keys := repo.GetCryptoService().ListKeys(data.CanonicalRootRole)
|
|
var rootKeyID string
|
|
// always select the first root key
|
|
if len(keys) > 0 {
|
|
sort.Strings(keys)
|
|
rootKeyID = keys[0]
|
|
} else {
|
|
rootPublicKey, err := repo.GetCryptoService().Create(data.CanonicalRootRole, "", data.ECDSAKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
rootKeyID = rootPublicKey.ID()
|
|
}
|
|
|
|
// Initialize the notary repository with a remotely managed snapshot key
|
|
if err := repo.Initialize([]string{rootKeyID}, data.CanonicalSnapshotRole); err != nil {
|
|
return NotaryError(repoInfo.Name.Name(), err)
|
|
}
|
|
_, _ = fmt.Fprintf(ioStreams.Out(), "Finished initializing %q\n", repoInfo.Name.Name())
|
|
err = repo.AddTarget(notaryTarget, data.CanonicalTargetsRole)
|
|
case nil:
|
|
// already initialized and we have successfully downloaded the latest metadata
|
|
err = AddToAllSignableRoles(repo, notaryTarget)
|
|
default:
|
|
return NotaryError(repoInfo.Name.Name(), err)
|
|
}
|
|
|
|
if err == nil {
|
|
err = repo.Publish()
|
|
}
|
|
|
|
if err != nil {
|
|
err = fmt.Errorf("failed to sign %s:%s: %w", repoInfo.Name.Name(), tag, err)
|
|
return NotaryError(repoInfo.Name.Name(), err)
|
|
}
|
|
|
|
_, _ = fmt.Fprintf(ioStreams.Out(), "Successfully signed %s:%s\n", repoInfo.Name.Name(), tag)
|
|
return nil
|
|
}
|