cli/command/image: deprecate PushTrustedReference, move to trust
This function was shared between "trust" "image" and "plugin" packages, all of which needed the trust package, so move it there instead. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
parent
c6f456bc90
commit
d80436021c
@ -139,8 +139,8 @@ To push the complete multi-platform image, remove the --platform flag.
|
|||||||
|
|
||||||
defer responseBody.Close()
|
defer responseBody.Close()
|
||||||
if !opts.untrusted {
|
if !opts.untrusted {
|
||||||
// TODO PushTrustedReference currently doesn't respect `--quiet`
|
// TODO pushTrustedReference currently doesn't respect `--quiet`
|
||||||
return PushTrustedReference(ctx, dockerCli, repoInfo, ref, authConfig, responseBody)
|
return pushTrustedReference(ctx, dockerCli, repoInfo, ref, authConfig, responseBody)
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.quiet {
|
if opts.quiet {
|
||||||
|
@ -3,17 +3,14 @@ package image
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/distribution/reference"
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/internal/jsonstream"
|
"github.com/docker/cli/cli/internal/jsonstream"
|
||||||
"github.com/docker/cli/cli/streams"
|
"github.com/docker/cli/cli/streams"
|
||||||
"github.com/docker/cli/cli/trust"
|
"github.com/docker/cli/cli/trust"
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/api/types/image"
|
"github.com/docker/docker/api/types/image"
|
||||||
registrytypes "github.com/docker/docker/api/types/registry"
|
registrytypes "github.com/docker/docker/api/types/registry"
|
||||||
"github.com/docker/docker/registry"
|
"github.com/docker/docker/registry"
|
||||||
@ -55,120 +52,19 @@ func TrustedPush(ctx context.Context, cli command.Cli, repoInfo *registry.Reposi
|
|||||||
|
|
||||||
defer responseBody.Close()
|
defer responseBody.Close()
|
||||||
|
|
||||||
return PushTrustedReference(ctx, cli, repoInfo, ref, authConfig, responseBody)
|
return trust.PushTrustedReference(ctx, cli, repoInfo, ref, authConfig, responseBody, command.UserAgent())
|
||||||
}
|
}
|
||||||
|
|
||||||
// PushTrustedReference pushes a canonical reference to the trust server.
|
// PushTrustedReference pushes a canonical reference to the trust server.
|
||||||
//
|
//
|
||||||
//nolint:gocyclo
|
// Deprecated: use [trust.PushTrustedReference] instead. this function was only used internally and will be removed in the next release.
|
||||||
func PushTrustedReference(ctx context.Context, ioStreams command.Streams, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig registrytypes.AuthConfig, in io.Reader) error {
|
func PushTrustedReference(ctx context.Context, ioStreams command.Streams, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig registrytypes.AuthConfig, in io.Reader) error {
|
||||||
// If it is a trusted push we would like to find the target entry which match the
|
return pushTrustedReference(ctx, ioStreams, repoInfo, ref, authConfig, in)
|
||||||
// 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 types.PushResult
|
// pushTrustedReference pushes a canonical reference to the trust server.
|
||||||
err := json.Unmarshal(*msg.Aux, &pushResult)
|
func pushTrustedReference(ctx context.Context, ioStreams command.Streams, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig registrytypes.AuthConfig, in io.Reader) error {
|
||||||
if err == nil && pushResult.Tag != "" {
|
return trust.PushTrustedReference(ctx, ioStreams, repoInfo, ref, authConfig, in, command.UserAgent())
|
||||||
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.Canonical:
|
|
||||||
return errors.New("cannot push a digest reference")
|
|
||||||
case reference.NamedTagged:
|
|
||||||
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.Errorf("internal error: only one call to handleTarget expected")
|
|
||||||
}
|
|
||||||
|
|
||||||
if notaryTarget == nil {
|
|
||||||
return errors.Errorf("no targets found, provide a specific tag in order to sign it")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _ = fmt.Fprintln(ioStreams.Out(), "Signing and pushing trust metadata")
|
|
||||||
|
|
||||||
repo, err := trust.GetNotaryRepository(ioStreams.In(), ioStreams.Out(), command.UserAgent(), repoInfo, &authConfig, "push", "pull")
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "error establishing connection to trust repository")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 trust.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 = trust.AddToAllSignableRoles(repo, notaryTarget)
|
|
||||||
default:
|
|
||||||
return trust.NotaryError(repoInfo.Name.Name(), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
err = repo.Publish()
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
err = errors.Wrapf(err, "failed to sign %s:%s", repoInfo.Name.Name(), tag)
|
|
||||||
return trust.NotaryError(repoInfo.Name.Name(), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _ = fmt.Fprintf(ioStreams.Out(), "Successfully signed %s:%s\n", repoInfo.Name.Name(), tag)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// trustedPull handles content trust pulling of an image
|
// trustedPull handles content trust pulling of an image
|
||||||
|
@ -6,8 +6,8 @@ import (
|
|||||||
"github.com/distribution/reference"
|
"github.com/distribution/reference"
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/image"
|
|
||||||
"github.com/docker/cli/cli/internal/jsonstream"
|
"github.com/docker/cli/cli/internal/jsonstream"
|
||||||
|
"github.com/docker/cli/cli/trust"
|
||||||
registrytypes "github.com/docker/docker/api/types/registry"
|
registrytypes "github.com/docker/docker/api/types/registry"
|
||||||
"github.com/docker/docker/registry"
|
"github.com/docker/docker/registry"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -66,7 +66,7 @@ func runPush(ctx context.Context, dockerCli command.Cli, opts pushOptions) error
|
|||||||
defer responseBody.Close()
|
defer responseBody.Close()
|
||||||
|
|
||||||
if !opts.untrusted {
|
if !opts.untrusted {
|
||||||
return image.PushTrustedReference(ctx, dockerCli, repoInfo, named, authConfig, responseBody)
|
return trust.PushTrustedReference(ctx, dockerCli, repoInfo, named, authConfig, responseBody, command.UserAgent())
|
||||||
}
|
}
|
||||||
|
|
||||||
return jsonstream.Display(ctx, responseBody, dockerCli.Out())
|
return jsonstream.Display(ctx, responseBody, dockerCli.Out())
|
||||||
|
@ -107,7 +107,7 @@ func runSignImage(ctx context.Context, dockerCLI command.Cli, options signOption
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer responseBody.Close()
|
defer responseBody.Close()
|
||||||
return image.PushTrustedReference(ctx, dockerCLI, imgRefAndAuth.RepoInfo(), imgRefAndAuth.Reference(), authConfig, responseBody)
|
return trust.PushTrustedReference(ctx, dockerCLI, imgRefAndAuth.RepoInfo(), imgRefAndAuth.Reference(), authConfig, responseBody, command.UserAgent())
|
||||||
default:
|
default:
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
143
cli/trust/trust_push.go
Normal file
143
cli/trust/trust_push.go
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
package trust
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
|
"github.com/docker/cli/cli/internal/jsonstream"
|
||||||
|
"github.com/docker/cli/cli/streams"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
registrytypes "github.com/docker/docker/api/types/registry"
|
||||||
|
"github.com/docker/docker/registry"
|
||||||
|
"github.com/opencontainers/go-digest"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
|
||||||
|
// PushTrustedReference pushes a canonical reference to the trust server.
|
||||||
|
//
|
||||||
|
//nolint:gocyclo
|
||||||
|
func PushTrustedReference(ctx context.Context, ioStreams Streams, repoInfo *registry.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 types.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.Canonical:
|
||||||
|
return errors.New("cannot push a digest reference")
|
||||||
|
case reference.NamedTagged:
|
||||||
|
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.Errorf("internal error: only one call to handleTarget expected")
|
||||||
|
}
|
||||||
|
|
||||||
|
if notaryTarget == nil {
|
||||||
|
return errors.Errorf("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 errors.Wrap(err, "error establishing connection to trust repository")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 = errors.Wrapf(err, "failed to sign %s:%s", repoInfo.Name.Name(), tag)
|
||||||
|
return NotaryError(repoInfo.Name.Name(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _ = fmt.Fprintf(ioStreams.Out(), "Successfully signed %s:%s\n", repoInfo.Name.Name(), tag)
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user