mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
cli: Pin image to digest using content trust
Implement notary-based digest lookup in the client when
DOCKER_CONTENT_TRUST=1.
Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
(cherry picked from commit d4d6f8c0d0
)
This commit is contained in:
parent
5e7d2ab3b8
commit
61dc897a30
4 changed files with 111 additions and 1 deletions
|
@ -72,6 +72,10 @@ func runCreate(dockerCli *command.DockerCli, opts *serviceOptions) error {
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
if err := resolveServiceImageDigest(dockerCli, &service); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// only send auth if flag was set
|
// only send auth if flag was set
|
||||||
if opts.registryAuth {
|
if opts.registryAuth {
|
||||||
// Retrieve encoded auth token from the image reference
|
// Retrieve encoded auth token from the image reference
|
||||||
|
|
96
cli/command/service/trust.go
Normal file
96
cli/command/service/trust.go
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/distribution/digest"
|
||||||
|
distreference "github.com/docker/distribution/reference"
|
||||||
|
"github.com/docker/docker/api/types/swarm"
|
||||||
|
"github.com/docker/docker/cli/command"
|
||||||
|
"github.com/docker/docker/cli/trust"
|
||||||
|
"github.com/docker/docker/reference"
|
||||||
|
"github.com/docker/docker/registry"
|
||||||
|
"github.com/docker/notary/tuf/data"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resolveServiceImageDigest(dockerCli *command.DockerCli, service *swarm.ServiceSpec) error {
|
||||||
|
if !command.IsTrusted() {
|
||||||
|
// Digests are resolved by the daemon when not using content
|
||||||
|
// trust.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
image := service.TaskTemplate.ContainerSpec.Image
|
||||||
|
|
||||||
|
// We only attempt to resolve the digest if the reference
|
||||||
|
// could be parsed as a digest reference. Specifying an image ID
|
||||||
|
// is valid but not resolvable. There is no warning message for
|
||||||
|
// an image ID because it's valid to use one.
|
||||||
|
if _, err := digest.ParseDigest(image); err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ref, err := reference.ParseNamed(image)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Could not parse image reference %s", service.TaskTemplate.ContainerSpec.Image)
|
||||||
|
}
|
||||||
|
if _, ok := ref.(reference.Canonical); !ok {
|
||||||
|
ref = reference.WithDefaultTag(ref)
|
||||||
|
|
||||||
|
taggedRef, ok := ref.(reference.NamedTagged)
|
||||||
|
if !ok {
|
||||||
|
// This should never happen because a reference either
|
||||||
|
// has a digest, or WithDefaultTag would give it a tag.
|
||||||
|
return errors.New("Failed to resolve image digest using content trust: reference is missing a tag")
|
||||||
|
}
|
||||||
|
|
||||||
|
resolvedImage, err := trustedResolveDigest(context.Background(), dockerCli, taggedRef)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to resolve image digest using content trust: %v", err)
|
||||||
|
}
|
||||||
|
logrus.Debugf("resolved image tag to %s using content trust", resolvedImage.String())
|
||||||
|
service.TaskTemplate.ContainerSpec.Image = resolvedImage.String()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func trustedResolveDigest(ctx context.Context, cli *command.DockerCli, ref reference.NamedTagged) (distreference.Canonical, error) {
|
||||||
|
repoInfo, err := registry.ParseRepositoryInfo(ref)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
authConfig := command.ResolveAuthConfig(ctx, cli, repoInfo.Index)
|
||||||
|
|
||||||
|
notaryRepo, err := trust.GetNotaryRepository(cli, repoInfo, authConfig, "pull")
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "error establishing connection to trust repository")
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := notaryRepo.GetTargetByName(ref.Tag(), trust.ReleasesRole, data.CanonicalTargetsRole)
|
||||||
|
if err != nil {
|
||||||
|
return nil, trust.NotaryError(repoInfo.FullName(), err)
|
||||||
|
}
|
||||||
|
// Only get the tag if it's in the top level targets role or the releases delegation role
|
||||||
|
// ignore it if it's in any other delegation roles
|
||||||
|
if t.Role != trust.ReleasesRole && t.Role != data.CanonicalTargetsRole {
|
||||||
|
return nil, trust.NotaryError(repoInfo.FullName(), fmt.Errorf("No trust data for %s", ref.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Debugf("retrieving target for %s role\n", t.Role)
|
||||||
|
h, ok := t.Hashes["sha256"]
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("no valid hash, expecting sha256")
|
||||||
|
}
|
||||||
|
|
||||||
|
dgst := digest.NewDigestFromHex("sha256", hex.EncodeToString(h))
|
||||||
|
|
||||||
|
// Using distribution reference package to make sure that adding a
|
||||||
|
// digest does not erase the tag. When the two reference packages
|
||||||
|
// are unified, this will no longer be an issue.
|
||||||
|
return distreference.WithDigest(ref, dgst)
|
||||||
|
}
|
|
@ -103,6 +103,12 @@ func runUpdate(dockerCli *command.DockerCli, flags *pflag.FlagSet, serviceID str
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if flags.Changed("image") {
|
||||||
|
if err := resolveServiceImageDigest(dockerCli, spec); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
updatedSecrets, err := getUpdatedSecrets(apiClient, flags, spec.TaskTemplate.ContainerSpec.Secrets)
|
updatedSecrets, err := getUpdatedSecrets(apiClient, flags, spec.TaskTemplate.ContainerSpec.Secrets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -1111,9 +1111,11 @@ func (c *Cluster) CreateService(s types.ServiceSpec, encodedAuth string) (*apity
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Warnf("unable to pin image %s to digest: %s", ctnr.Image, err.Error())
|
logrus.Warnf("unable to pin image %s to digest: %s", ctnr.Image, err.Error())
|
||||||
resp.Warnings = append(resp.Warnings, fmt.Sprintf("unable to pin image %s to digest: %s", ctnr.Image, err.Error()))
|
resp.Warnings = append(resp.Warnings, fmt.Sprintf("unable to pin image %s to digest: %s", ctnr.Image, err.Error()))
|
||||||
} else {
|
} else if ctnr.Image != digestImage {
|
||||||
logrus.Debugf("pinning image %s by digest: %s", ctnr.Image, digestImage)
|
logrus.Debugf("pinning image %s by digest: %s", ctnr.Image, digestImage)
|
||||||
ctnr.Image = digestImage
|
ctnr.Image = digestImage
|
||||||
|
} else {
|
||||||
|
logrus.Debugf("creating service using supplied digest reference %s", ctnr.Image)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1223,6 +1225,8 @@ func (c *Cluster) UpdateService(serviceIDOrName string, version uint64, spec typ
|
||||||
} else if newCtnr.Image != digestImage {
|
} else if newCtnr.Image != digestImage {
|
||||||
logrus.Debugf("pinning image %s by digest: %s", newCtnr.Image, digestImage)
|
logrus.Debugf("pinning image %s by digest: %s", newCtnr.Image, digestImage)
|
||||||
newCtnr.Image = digestImage
|
newCtnr.Image = digestImage
|
||||||
|
} else {
|
||||||
|
logrus.Debugf("updating service using supplied digest reference %s", newCtnr.Image)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue