github.com/AliyunContainerService/cli@v0.0.0-20181009023821-814ced4b30d0/cli/command/image/trust.go (about) 1 package image 2 3 import ( 4 "context" 5 "encoding/hex" 6 "encoding/json" 7 "fmt" 8 "io" 9 "sort" 10 11 "github.com/docker/cli/cli/command" 12 "github.com/docker/cli/cli/trust" 13 "github.com/docker/distribution/reference" 14 "github.com/docker/docker/api/types" 15 registrytypes "github.com/docker/docker/api/types/registry" 16 "github.com/docker/docker/pkg/jsonmessage" 17 "github.com/docker/docker/registry" 18 digest "github.com/opencontainers/go-digest" 19 "github.com/pkg/errors" 20 "github.com/sirupsen/logrus" 21 "github.com/theupdateframework/notary/client" 22 "github.com/theupdateframework/notary/tuf/data" 23 ) 24 25 type target struct { 26 name string 27 digest digest.Digest 28 size int64 29 } 30 31 // TrustedPush handles content trust pushing of an image 32 func TrustedPush(ctx context.Context, cli command.Cli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error { 33 responseBody, err := imagePushPrivileged(ctx, cli, authConfig, ref, requestPrivilege) 34 if err != nil { 35 return err 36 } 37 38 defer responseBody.Close() 39 40 return PushTrustedReference(cli, repoInfo, ref, authConfig, responseBody) 41 } 42 43 // PushTrustedReference pushes a canonical reference to the trust server. 44 // nolint: gocyclo 45 func PushTrustedReference(streams command.Streams, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, in io.Reader) error { 46 // If it is a trusted push we would like to find the target entry which match the 47 // tag provided in the function and then do an AddTarget later. 48 target := &client.Target{} 49 // Count the times of calling for handleTarget, 50 // if it is called more that once, that should be considered an error in a trusted push. 51 cnt := 0 52 handleTarget := func(msg jsonmessage.JSONMessage) { 53 cnt++ 54 if cnt > 1 { 55 // handleTarget should only be called once. This will be treated as an error. 56 return 57 } 58 59 var pushResult types.PushResult 60 err := json.Unmarshal(*msg.Aux, &pushResult) 61 if err == nil && pushResult.Tag != "" { 62 if dgst, err := digest.Parse(pushResult.Digest); err == nil { 63 h, err := hex.DecodeString(dgst.Hex()) 64 if err != nil { 65 target = nil 66 return 67 } 68 target.Name = pushResult.Tag 69 target.Hashes = data.Hashes{string(dgst.Algorithm()): h} 70 target.Length = int64(pushResult.Size) 71 } 72 } 73 } 74 75 var tag string 76 switch x := ref.(type) { 77 case reference.Canonical: 78 return errors.New("cannot push a digest reference") 79 case reference.NamedTagged: 80 tag = x.Tag() 81 default: 82 // We want trust signatures to always take an explicit tag, 83 // otherwise it will act as an untrusted push. 84 if err := jsonmessage.DisplayJSONMessagesToStream(in, streams.Out(), nil); err != nil { 85 return err 86 } 87 fmt.Fprintln(streams.Err(), "No tag specified, skipping trust metadata push") 88 return nil 89 } 90 91 if err := jsonmessage.DisplayJSONMessagesToStream(in, streams.Out(), handleTarget); err != nil { 92 return err 93 } 94 95 if cnt > 1 { 96 return errors.Errorf("internal error: only one call to handleTarget expected") 97 } 98 99 if target == nil { 100 return errors.Errorf("no targets found, please provide a specific tag in order to sign it") 101 } 102 103 fmt.Fprintln(streams.Out(), "Signing and pushing trust metadata") 104 105 repo, err := trust.GetNotaryRepository(streams.In(), streams.Out(), command.UserAgent(), repoInfo, &authConfig, "push", "pull") 106 if err != nil { 107 return errors.Wrap(err, "error establishing connection to trust repository") 108 } 109 110 // get the latest repository metadata so we can figure out which roles to sign 111 _, err = repo.ListTargets() 112 113 switch err.(type) { 114 case client.ErrRepoNotInitialized, client.ErrRepositoryNotExist: 115 keys := repo.GetCryptoService().ListKeys(data.CanonicalRootRole) 116 var rootKeyID string 117 // always select the first root key 118 if len(keys) > 0 { 119 sort.Strings(keys) 120 rootKeyID = keys[0] 121 } else { 122 rootPublicKey, err := repo.GetCryptoService().Create(data.CanonicalRootRole, "", data.ECDSAKey) 123 if err != nil { 124 return err 125 } 126 rootKeyID = rootPublicKey.ID() 127 } 128 129 // Initialize the notary repository with a remotely managed snapshot key 130 if err := repo.Initialize([]string{rootKeyID}, data.CanonicalSnapshotRole); err != nil { 131 return trust.NotaryError(repoInfo.Name.Name(), err) 132 } 133 fmt.Fprintf(streams.Out(), "Finished initializing %q\n", repoInfo.Name.Name()) 134 err = repo.AddTarget(target, data.CanonicalTargetsRole) 135 case nil: 136 // already initialized and we have successfully downloaded the latest metadata 137 err = AddTargetToAllSignableRoles(repo, target) 138 default: 139 return trust.NotaryError(repoInfo.Name.Name(), err) 140 } 141 142 if err == nil { 143 err = repo.Publish() 144 } 145 146 if err != nil { 147 err = errors.Wrapf(err, "failed to sign %s:%s", repoInfo.Name.Name(), tag) 148 return trust.NotaryError(repoInfo.Name.Name(), err) 149 } 150 151 fmt.Fprintf(streams.Out(), "Successfully signed %s:%s\n", repoInfo.Name.Name(), tag) 152 return nil 153 } 154 155 // AddTargetToAllSignableRoles attempts to add the image target to all the top level delegation roles we can 156 // (based on whether we have the signing key and whether the role's path allows 157 // us to). 158 // If there are no delegation roles, we add to the targets role. 159 func AddTargetToAllSignableRoles(repo client.Repository, target *client.Target) error { 160 signableRoles, err := trust.GetSignableRoles(repo, target) 161 if err != nil { 162 return err 163 } 164 165 return repo.AddTarget(target, signableRoles...) 166 } 167 168 // imagePushPrivileged push the image 169 func imagePushPrivileged(ctx context.Context, cli command.Cli, authConfig types.AuthConfig, ref reference.Reference, requestPrivilege types.RequestPrivilegeFunc) (io.ReadCloser, error) { 170 encodedAuth, err := command.EncodeAuthToBase64(authConfig) 171 if err != nil { 172 return nil, err 173 } 174 options := types.ImagePushOptions{ 175 RegistryAuth: encodedAuth, 176 PrivilegeFunc: requestPrivilege, 177 } 178 179 return cli.Client().ImagePush(ctx, reference.FamiliarString(ref), options) 180 } 181 182 // trustedPull handles content trust pulling of an image 183 func trustedPull(ctx context.Context, cli command.Cli, imgRefAndAuth trust.ImageRefAndAuth, platform string) error { 184 refs, err := getTrustedPullTargets(cli, imgRefAndAuth) 185 if err != nil { 186 return err 187 } 188 189 ref := imgRefAndAuth.Reference() 190 for i, r := range refs { 191 displayTag := r.name 192 if displayTag != "" { 193 displayTag = ":" + displayTag 194 } 195 fmt.Fprintf(cli.Out(), "Pull (%d of %d): %s%s@%s\n", i+1, len(refs), reference.FamiliarName(ref), displayTag, r.digest) 196 197 trustedRef, err := reference.WithDigest(reference.TrimNamed(ref), r.digest) 198 if err != nil { 199 return err 200 } 201 updatedImgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, nil, AuthResolver(cli), trustedRef.String()) 202 if err != nil { 203 return err 204 } 205 if err := imagePullPrivileged(ctx, cli, updatedImgRefAndAuth, false, platform); err != nil { 206 return err 207 } 208 209 tagged, err := reference.WithTag(reference.TrimNamed(ref), r.name) 210 if err != nil { 211 return err 212 } 213 214 if err := TagTrusted(ctx, cli, trustedRef, tagged); err != nil { 215 return err 216 } 217 } 218 return nil 219 } 220 221 func getTrustedPullTargets(cli command.Cli, imgRefAndAuth trust.ImageRefAndAuth) ([]target, error) { 222 notaryRepo, err := cli.NotaryClient(imgRefAndAuth, trust.ActionsPullOnly) 223 if err != nil { 224 return nil, errors.Wrap(err, "error establishing connection to trust repository") 225 } 226 227 ref := imgRefAndAuth.Reference() 228 tagged, isTagged := ref.(reference.NamedTagged) 229 if !isTagged { 230 // List all targets 231 targets, err := notaryRepo.ListTargets(trust.ReleasesRole, data.CanonicalTargetsRole) 232 if err != nil { 233 return nil, trust.NotaryError(ref.Name(), err) 234 } 235 var refs []target 236 for _, tgt := range targets { 237 t, err := convertTarget(tgt.Target) 238 if err != nil { 239 fmt.Fprintf(cli.Err(), "Skipping target for %q\n", reference.FamiliarName(ref)) 240 continue 241 } 242 // Only list tags in the top level targets role or the releases delegation role - ignore 243 // all other delegation roles 244 if tgt.Role != trust.ReleasesRole && tgt.Role != data.CanonicalTargetsRole { 245 continue 246 } 247 refs = append(refs, t) 248 } 249 if len(refs) == 0 { 250 return nil, trust.NotaryError(ref.Name(), errors.Errorf("No trusted tags for %s", ref.Name())) 251 } 252 return refs, nil 253 } 254 255 t, err := notaryRepo.GetTargetByName(tagged.Tag(), trust.ReleasesRole, data.CanonicalTargetsRole) 256 if err != nil { 257 return nil, trust.NotaryError(ref.Name(), err) 258 } 259 // Only get the tag if it's in the top level targets role or the releases delegation role 260 // ignore it if it's in any other delegation roles 261 if t.Role != trust.ReleasesRole && t.Role != data.CanonicalTargetsRole { 262 return nil, trust.NotaryError(ref.Name(), errors.Errorf("No trust data for %s", tagged.Tag())) 263 } 264 265 logrus.Debugf("retrieving target for %s role", t.Role) 266 r, err := convertTarget(t.Target) 267 return []target{r}, err 268 } 269 270 // imagePullPrivileged pulls the image and displays it to the output 271 func imagePullPrivileged(ctx context.Context, cli command.Cli, imgRefAndAuth trust.ImageRefAndAuth, all bool, platform string) error { 272 ref := reference.FamiliarString(imgRefAndAuth.Reference()) 273 274 encodedAuth, err := command.EncodeAuthToBase64(*imgRefAndAuth.AuthConfig()) 275 if err != nil { 276 return err 277 } 278 requestPrivilege := command.RegistryAuthenticationPrivilegedFunc(cli, imgRefAndAuth.RepoInfo().Index, "pull") 279 options := types.ImagePullOptions{ 280 RegistryAuth: encodedAuth, 281 PrivilegeFunc: requestPrivilege, 282 All: all, 283 Platform: platform, 284 } 285 responseBody, err := cli.Client().ImagePull(ctx, ref, options) 286 if err != nil { 287 return err 288 } 289 defer responseBody.Close() 290 291 return jsonmessage.DisplayJSONMessagesToStream(responseBody, cli.Out(), nil) 292 } 293 294 // TrustedReference returns the canonical trusted reference for an image reference 295 func TrustedReference(ctx context.Context, cli command.Cli, ref reference.NamedTagged, rs registry.Service) (reference.Canonical, error) { 296 imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, rs, AuthResolver(cli), ref.String()) 297 if err != nil { 298 return nil, err 299 } 300 301 notaryRepo, err := cli.NotaryClient(imgRefAndAuth, []string{"pull"}) 302 if err != nil { 303 return nil, errors.Wrap(err, "error establishing connection to trust repository") 304 } 305 306 t, err := notaryRepo.GetTargetByName(ref.Tag(), trust.ReleasesRole, data.CanonicalTargetsRole) 307 if err != nil { 308 return nil, trust.NotaryError(imgRefAndAuth.RepoInfo().Name.Name(), err) 309 } 310 // Only list tags in the top level targets role or the releases delegation role - ignore 311 // all other delegation roles 312 if t.Role != trust.ReleasesRole && t.Role != data.CanonicalTargetsRole { 313 return nil, trust.NotaryError(imgRefAndAuth.RepoInfo().Name.Name(), client.ErrNoSuchTarget(ref.Tag())) 314 } 315 r, err := convertTarget(t.Target) 316 if err != nil { 317 return nil, err 318 319 } 320 return reference.WithDigest(reference.TrimNamed(ref), r.digest) 321 } 322 323 func convertTarget(t client.Target) (target, error) { 324 h, ok := t.Hashes["sha256"] 325 if !ok { 326 return target{}, errors.New("no valid hash, expecting sha256") 327 } 328 return target{ 329 name: t.Name, 330 digest: digest.NewDigestFromHex("sha256", hex.EncodeToString(h)), 331 size: t.Length, 332 }, nil 333 } 334 335 // TagTrusted tags a trusted ref 336 // nolint: interfacer 337 func TagTrusted(ctx context.Context, cli command.Cli, trustedRef reference.Canonical, ref reference.NamedTagged) error { 338 // Use familiar references when interacting with client and output 339 familiarRef := reference.FamiliarString(ref) 340 trustedFamiliarRef := reference.FamiliarString(trustedRef) 341 342 fmt.Fprintf(cli.Err(), "Tagging %s as %s\n", trustedFamiliarRef, familiarRef) 343 344 return cli.Client().ImageTag(ctx, trustedFamiliarRef, familiarRef) 345 } 346 347 // AuthResolver returns an auth resolver function from a command.Cli 348 func AuthResolver(cli command.Cli) func(ctx context.Context, index *registrytypes.IndexInfo) types.AuthConfig { 349 return func(ctx context.Context, index *registrytypes.IndexInfo) types.AuthConfig { 350 return command.ResolveAuthConfig(ctx, cli, index) 351 } 352 }