|
@@ -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)
|
|
|
+}
|