github.com/portworx/docker@v1.12.1/api/client/trust.go (about) 1 package client 2 3 import ( 4 "encoding/hex" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "io" 9 "net" 10 "net/http" 11 "net/url" 12 "os" 13 "path" 14 "path/filepath" 15 "sort" 16 "strconv" 17 "time" 18 19 "golang.org/x/net/context" 20 21 "github.com/Sirupsen/logrus" 22 "github.com/docker/distribution/digest" 23 "github.com/docker/distribution/registry/client/auth" 24 "github.com/docker/distribution/registry/client/transport" 25 "github.com/docker/docker/cliconfig" 26 "github.com/docker/docker/distribution" 27 "github.com/docker/docker/pkg/jsonmessage" 28 flag "github.com/docker/docker/pkg/mflag" 29 "github.com/docker/docker/reference" 30 "github.com/docker/docker/registry" 31 "github.com/docker/engine-api/types" 32 registrytypes "github.com/docker/engine-api/types/registry" 33 "github.com/docker/go-connections/tlsconfig" 34 "github.com/docker/notary/client" 35 "github.com/docker/notary/passphrase" 36 "github.com/docker/notary/trustmanager" 37 "github.com/docker/notary/trustpinning" 38 "github.com/docker/notary/tuf/data" 39 "github.com/docker/notary/tuf/signed" 40 "github.com/docker/notary/tuf/store" 41 "github.com/spf13/pflag" 42 ) 43 44 var ( 45 releasesRole = path.Join(data.CanonicalTargetsRole, "releases") 46 untrusted bool 47 ) 48 49 // addTrustedFlags is the mflag version of AddTrustedFlags 50 func addTrustedFlags(fs *flag.FlagSet, verify bool) { 51 trusted, message := setupTrustedFlag(verify) 52 fs.BoolVar(&untrusted, []string{"-disable-content-trust"}, !trusted, message) 53 } 54 55 // AddTrustedFlags adds content trust flags to the current command flagset 56 func AddTrustedFlags(fs *pflag.FlagSet, verify bool) { 57 trusted, message := setupTrustedFlag(verify) 58 fs.BoolVar(&untrusted, "disable-content-trust", !trusted, message) 59 } 60 61 func setupTrustedFlag(verify bool) (bool, string) { 62 var trusted bool 63 if e := os.Getenv("DOCKER_CONTENT_TRUST"); e != "" { 64 if t, err := strconv.ParseBool(e); t || err != nil { 65 // treat any other value as true 66 trusted = true 67 } 68 } 69 message := "Skip image signing" 70 if verify { 71 message = "Skip image verification" 72 } 73 return trusted, message 74 } 75 76 // IsTrusted returns true if content trust is enabled 77 func IsTrusted() bool { 78 return !untrusted 79 } 80 81 type target struct { 82 reference registry.Reference 83 digest digest.Digest 84 size int64 85 } 86 87 func (cli *DockerCli) trustDirectory() string { 88 return filepath.Join(cliconfig.ConfigDir(), "trust") 89 } 90 91 // certificateDirectory returns the directory containing 92 // TLS certificates for the given server. An error is 93 // returned if there was an error parsing the server string. 94 func (cli *DockerCli) certificateDirectory(server string) (string, error) { 95 u, err := url.Parse(server) 96 if err != nil { 97 return "", err 98 } 99 100 return filepath.Join(cliconfig.ConfigDir(), "tls", u.Host), nil 101 } 102 103 func trustServer(index *registrytypes.IndexInfo) (string, error) { 104 if s := os.Getenv("DOCKER_CONTENT_TRUST_SERVER"); s != "" { 105 urlObj, err := url.Parse(s) 106 if err != nil || urlObj.Scheme != "https" { 107 return "", fmt.Errorf("valid https URL required for trust server, got %s", s) 108 } 109 110 return s, nil 111 } 112 if index.Official { 113 return registry.NotaryServer, nil 114 } 115 return "https://" + index.Name, nil 116 } 117 118 type simpleCredentialStore struct { 119 auth types.AuthConfig 120 } 121 122 func (scs simpleCredentialStore) Basic(u *url.URL) (string, string) { 123 return scs.auth.Username, scs.auth.Password 124 } 125 126 func (scs simpleCredentialStore) RefreshToken(u *url.URL, service string) string { 127 return scs.auth.IdentityToken 128 } 129 130 func (scs simpleCredentialStore) SetRefreshToken(*url.URL, string, string) { 131 } 132 133 // getNotaryRepository returns a NotaryRepository which stores all the 134 // information needed to operate on a notary repository. 135 // It creates an HTTP transport providing authentication support. 136 func (cli *DockerCli) getNotaryRepository(repoInfo *registry.RepositoryInfo, authConfig types.AuthConfig, actions ...string) (*client.NotaryRepository, error) { 137 server, err := trustServer(repoInfo.Index) 138 if err != nil { 139 return nil, err 140 } 141 142 var cfg = tlsconfig.ClientDefault 143 cfg.InsecureSkipVerify = !repoInfo.Index.Secure 144 145 // Get certificate base directory 146 certDir, err := cli.certificateDirectory(server) 147 if err != nil { 148 return nil, err 149 } 150 logrus.Debugf("reading certificate directory: %s", certDir) 151 152 if err := registry.ReadCertsDirectory(&cfg, certDir); err != nil { 153 return nil, err 154 } 155 156 base := &http.Transport{ 157 Proxy: http.ProxyFromEnvironment, 158 Dial: (&net.Dialer{ 159 Timeout: 30 * time.Second, 160 KeepAlive: 30 * time.Second, 161 DualStack: true, 162 }).Dial, 163 TLSHandshakeTimeout: 10 * time.Second, 164 TLSClientConfig: &cfg, 165 DisableKeepAlives: true, 166 } 167 168 // Skip configuration headers since request is not going to Docker daemon 169 modifiers := registry.DockerHeaders(clientUserAgent(), http.Header{}) 170 authTransport := transport.NewTransport(base, modifiers...) 171 pingClient := &http.Client{ 172 Transport: authTransport, 173 Timeout: 5 * time.Second, 174 } 175 endpointStr := server + "/v2/" 176 req, err := http.NewRequest("GET", endpointStr, nil) 177 if err != nil { 178 return nil, err 179 } 180 181 challengeManager := auth.NewSimpleChallengeManager() 182 183 resp, err := pingClient.Do(req) 184 if err != nil { 185 // Ignore error on ping to operate in offline mode 186 logrus.Debugf("Error pinging notary server %q: %s", endpointStr, err) 187 } else { 188 defer resp.Body.Close() 189 190 // Add response to the challenge manager to parse out 191 // authentication header and register authentication method 192 if err := challengeManager.AddResponse(resp); err != nil { 193 return nil, err 194 } 195 } 196 197 creds := simpleCredentialStore{auth: authConfig} 198 tokenHandler := auth.NewTokenHandler(authTransport, creds, repoInfo.FullName(), actions...) 199 basicHandler := auth.NewBasicHandler(creds) 200 modifiers = append(modifiers, transport.RequestModifier(auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler))) 201 tr := transport.NewTransport(base, modifiers...) 202 203 return client.NewNotaryRepository( 204 cli.trustDirectory(), repoInfo.FullName(), server, tr, cli.getPassphraseRetriever(), 205 trustpinning.TrustPinConfig{}) 206 } 207 208 func convertTarget(t client.Target) (target, error) { 209 h, ok := t.Hashes["sha256"] 210 if !ok { 211 return target{}, errors.New("no valid hash, expecting sha256") 212 } 213 return target{ 214 reference: registry.ParseReference(t.Name), 215 digest: digest.NewDigestFromHex("sha256", hex.EncodeToString(h)), 216 size: t.Length, 217 }, nil 218 } 219 220 func (cli *DockerCli) getPassphraseRetriever() passphrase.Retriever { 221 aliasMap := map[string]string{ 222 "root": "root", 223 "snapshot": "repository", 224 "targets": "repository", 225 "default": "repository", 226 } 227 baseRetriever := passphrase.PromptRetrieverWithInOut(cli.in, cli.out, aliasMap) 228 env := map[string]string{ 229 "root": os.Getenv("DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE"), 230 "snapshot": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"), 231 "targets": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"), 232 "default": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"), 233 } 234 235 return func(keyName string, alias string, createNew bool, numAttempts int) (string, bool, error) { 236 if v := env[alias]; v != "" { 237 return v, numAttempts > 1, nil 238 } 239 // For non-root roles, we can also try the "default" alias if it is specified 240 if v := env["default"]; v != "" && alias != data.CanonicalRootRole { 241 return v, numAttempts > 1, nil 242 } 243 return baseRetriever(keyName, alias, createNew, numAttempts) 244 } 245 } 246 247 // TrustedReference returns the canonical trusted reference for an image reference 248 func (cli *DockerCli) TrustedReference(ctx context.Context, ref reference.NamedTagged) (reference.Canonical, error) { 249 repoInfo, err := registry.ParseRepositoryInfo(ref) 250 if err != nil { 251 return nil, err 252 } 253 254 // Resolve the Auth config relevant for this server 255 authConfig := cli.ResolveAuthConfig(ctx, repoInfo.Index) 256 257 notaryRepo, err := cli.getNotaryRepository(repoInfo, authConfig, "pull") 258 if err != nil { 259 fmt.Fprintf(cli.out, "Error establishing connection to trust repository: %s\n", err) 260 return nil, err 261 } 262 263 t, err := notaryRepo.GetTargetByName(ref.Tag(), releasesRole, data.CanonicalTargetsRole) 264 if err != nil { 265 return nil, err 266 } 267 // Only list tags in the top level targets role or the releases delegation role - ignore 268 // all other delegation roles 269 if t.Role != releasesRole && t.Role != data.CanonicalTargetsRole { 270 return nil, notaryError(repoInfo.FullName(), fmt.Errorf("No trust data for %s", ref.Tag())) 271 } 272 r, err := convertTarget(t.Target) 273 if err != nil { 274 return nil, err 275 276 } 277 278 return reference.WithDigest(ref, r.digest) 279 } 280 281 // TagTrusted tags a trusted ref 282 func (cli *DockerCli) TagTrusted(ctx context.Context, trustedRef reference.Canonical, ref reference.NamedTagged) error { 283 fmt.Fprintf(cli.out, "Tagging %s as %s\n", trustedRef.String(), ref.String()) 284 285 return cli.client.ImageTag(ctx, trustedRef.String(), ref.String()) 286 } 287 288 func notaryError(repoName string, err error) error { 289 switch err.(type) { 290 case *json.SyntaxError: 291 logrus.Debugf("Notary syntax error: %s", err) 292 return fmt.Errorf("Error: no trust data available for remote repository %s. Try running notary server and setting DOCKER_CONTENT_TRUST_SERVER to its HTTPS address?", repoName) 293 case signed.ErrExpired: 294 return fmt.Errorf("Error: remote repository %s out-of-date: %v", repoName, err) 295 case trustmanager.ErrKeyNotFound: 296 return fmt.Errorf("Error: signing keys for remote repository %s not found: %v", repoName, err) 297 case *net.OpError: 298 return fmt.Errorf("Error: error contacting notary server: %v", err) 299 case store.ErrMetaNotFound: 300 return fmt.Errorf("Error: trust data missing for remote repository %s or remote repository not found: %v", repoName, err) 301 case signed.ErrInvalidKeyType: 302 return fmt.Errorf("Warning: potential malicious behavior - trust data mismatch for remote repository %s: %v", repoName, err) 303 case signed.ErrNoKeys: 304 return fmt.Errorf("Error: could not find signing keys for remote repository %s, or could not decrypt signing key: %v", repoName, err) 305 case signed.ErrLowVersion: 306 return fmt.Errorf("Warning: potential malicious behavior - trust data version is lower than expected for remote repository %s: %v", repoName, err) 307 case signed.ErrRoleThreshold: 308 return fmt.Errorf("Warning: potential malicious behavior - trust data has insufficient signatures for remote repository %s: %v", repoName, err) 309 case client.ErrRepositoryNotExist: 310 return fmt.Errorf("Error: remote trust data does not exist for %s: %v", repoName, err) 311 case signed.ErrInsufficientSignatures: 312 return fmt.Errorf("Error: could not produce valid signature for %s. If Yubikey was used, was touch input provided?: %v", repoName, err) 313 } 314 315 return err 316 } 317 318 // TrustedPull handles content trust pulling of an image 319 func (cli *DockerCli) TrustedPull(ctx context.Context, repoInfo *registry.RepositoryInfo, ref registry.Reference, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error { 320 var refs []target 321 322 notaryRepo, err := cli.getNotaryRepository(repoInfo, authConfig, "pull") 323 if err != nil { 324 fmt.Fprintf(cli.out, "Error establishing connection to trust repository: %s\n", err) 325 return err 326 } 327 328 if ref.String() == "" { 329 // List all targets 330 targets, err := notaryRepo.ListTargets(releasesRole, data.CanonicalTargetsRole) 331 if err != nil { 332 return notaryError(repoInfo.FullName(), err) 333 } 334 for _, tgt := range targets { 335 t, err := convertTarget(tgt.Target) 336 if err != nil { 337 fmt.Fprintf(cli.out, "Skipping target for %q\n", repoInfo.Name()) 338 continue 339 } 340 // Only list tags in the top level targets role or the releases delegation role - ignore 341 // all other delegation roles 342 if tgt.Role != releasesRole && tgt.Role != data.CanonicalTargetsRole { 343 continue 344 } 345 refs = append(refs, t) 346 } 347 if len(refs) == 0 { 348 return notaryError(repoInfo.FullName(), fmt.Errorf("No trusted tags for %s", repoInfo.FullName())) 349 } 350 } else { 351 t, err := notaryRepo.GetTargetByName(ref.String(), releasesRole, data.CanonicalTargetsRole) 352 if err != nil { 353 return notaryError(repoInfo.FullName(), err) 354 } 355 // Only get the tag if it's in the top level targets role or the releases delegation role 356 // ignore it if it's in any other delegation roles 357 if t.Role != releasesRole && t.Role != data.CanonicalTargetsRole { 358 return notaryError(repoInfo.FullName(), fmt.Errorf("No trust data for %s", ref.String())) 359 } 360 361 logrus.Debugf("retrieving target for %s role\n", t.Role) 362 r, err := convertTarget(t.Target) 363 if err != nil { 364 return err 365 366 } 367 refs = append(refs, r) 368 } 369 370 for i, r := range refs { 371 displayTag := r.reference.String() 372 if displayTag != "" { 373 displayTag = ":" + displayTag 374 } 375 fmt.Fprintf(cli.out, "Pull (%d of %d): %s%s@%s\n", i+1, len(refs), repoInfo.Name(), displayTag, r.digest) 376 377 ref, err := reference.WithDigest(repoInfo, r.digest) 378 if err != nil { 379 return err 380 } 381 if err := cli.ImagePullPrivileged(ctx, authConfig, ref.String(), requestPrivilege, false); err != nil { 382 return err 383 } 384 385 // If reference is not trusted, tag by trusted reference 386 if !r.reference.HasDigest() { 387 tagged, err := reference.WithTag(repoInfo, r.reference.String()) 388 if err != nil { 389 return err 390 } 391 trustedRef, err := reference.WithDigest(repoInfo, r.digest) 392 if err != nil { 393 return err 394 } 395 if err := cli.TagTrusted(ctx, trustedRef, tagged); err != nil { 396 return err 397 } 398 } 399 } 400 return nil 401 } 402 403 // TrustedPush handles content trust pushing of an image 404 func (cli *DockerCli) TrustedPush(ctx context.Context, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error { 405 responseBody, err := cli.ImagePushPrivileged(ctx, authConfig, ref.String(), requestPrivilege) 406 if err != nil { 407 return err 408 } 409 410 defer responseBody.Close() 411 412 // If it is a trusted push we would like to find the target entry which match the 413 // tag provided in the function and then do an AddTarget later. 414 target := &client.Target{} 415 // Count the times of calling for handleTarget, 416 // if it is called more that once, that should be considered an error in a trusted push. 417 cnt := 0 418 handleTarget := func(aux *json.RawMessage) { 419 cnt++ 420 if cnt > 1 { 421 // handleTarget should only be called one. This will be treated as an error. 422 return 423 } 424 425 var pushResult distribution.PushResult 426 err := json.Unmarshal(*aux, &pushResult) 427 if err == nil && pushResult.Tag != "" && pushResult.Digest.Validate() == nil { 428 h, err := hex.DecodeString(pushResult.Digest.Hex()) 429 if err != nil { 430 target = nil 431 return 432 } 433 target.Name = registry.ParseReference(pushResult.Tag).String() 434 target.Hashes = data.Hashes{string(pushResult.Digest.Algorithm()): h} 435 target.Length = int64(pushResult.Size) 436 } 437 } 438 439 var tag string 440 switch x := ref.(type) { 441 case reference.Canonical: 442 return errors.New("cannot push a digest reference") 443 case reference.NamedTagged: 444 tag = x.Tag() 445 } 446 447 // We want trust signatures to always take an explicit tag, 448 // otherwise it will act as an untrusted push. 449 if tag == "" { 450 if err = jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut, nil); err != nil { 451 return err 452 } 453 fmt.Fprintln(cli.out, "No tag specified, skipping trust metadata push") 454 return nil 455 } 456 457 if err = jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut, handleTarget); err != nil { 458 return err 459 } 460 461 if cnt > 1 { 462 return fmt.Errorf("internal error: only one call to handleTarget expected") 463 } 464 465 if target == nil { 466 fmt.Fprintln(cli.out, "No targets found, please provide a specific tag in order to sign it") 467 return nil 468 } 469 470 fmt.Fprintln(cli.out, "Signing and pushing trust metadata") 471 472 repo, err := cli.getNotaryRepository(repoInfo, authConfig, "push", "pull") 473 if err != nil { 474 fmt.Fprintf(cli.out, "Error establishing connection to notary repository: %s\n", err) 475 return err 476 } 477 478 // get the latest repository metadata so we can figure out which roles to sign 479 err = repo.Update(false) 480 481 switch err.(type) { 482 case client.ErrRepoNotInitialized, client.ErrRepositoryNotExist: 483 keys := repo.CryptoService.ListKeys(data.CanonicalRootRole) 484 var rootKeyID string 485 // always select the first root key 486 if len(keys) > 0 { 487 sort.Strings(keys) 488 rootKeyID = keys[0] 489 } else { 490 rootPublicKey, err := repo.CryptoService.Create(data.CanonicalRootRole, "", data.ECDSAKey) 491 if err != nil { 492 return err 493 } 494 rootKeyID = rootPublicKey.ID() 495 } 496 497 // Initialize the notary repository with a remotely managed snapshot key 498 if err := repo.Initialize(rootKeyID, data.CanonicalSnapshotRole); err != nil { 499 return notaryError(repoInfo.FullName(), err) 500 } 501 fmt.Fprintf(cli.out, "Finished initializing %q\n", repoInfo.FullName()) 502 err = repo.AddTarget(target, data.CanonicalTargetsRole) 503 case nil: 504 // already initialized and we have successfully downloaded the latest metadata 505 err = cli.addTargetToAllSignableRoles(repo, target) 506 default: 507 return notaryError(repoInfo.FullName(), err) 508 } 509 510 if err == nil { 511 err = repo.Publish() 512 } 513 514 if err != nil { 515 fmt.Fprintf(cli.out, "Failed to sign %q:%s - %s\n", repoInfo.FullName(), tag, err.Error()) 516 return notaryError(repoInfo.FullName(), err) 517 } 518 519 fmt.Fprintf(cli.out, "Successfully signed %q:%s\n", repoInfo.FullName(), tag) 520 return nil 521 } 522 523 // Attempt to add the image target to all the top level delegation roles we can 524 // (based on whether we have the signing key and whether the role's path allows 525 // us to). 526 // If there are no delegation roles, we add to the targets role. 527 func (cli *DockerCli) addTargetToAllSignableRoles(repo *client.NotaryRepository, target *client.Target) error { 528 var signableRoles []string 529 530 // translate the full key names, which includes the GUN, into just the key IDs 531 allCanonicalKeyIDs := make(map[string]struct{}) 532 for fullKeyID := range repo.CryptoService.ListAllKeys() { 533 allCanonicalKeyIDs[path.Base(fullKeyID)] = struct{}{} 534 } 535 536 allDelegationRoles, err := repo.GetDelegationRoles() 537 if err != nil { 538 return err 539 } 540 541 // if there are no delegation roles, then just try to sign it into the targets role 542 if len(allDelegationRoles) == 0 { 543 return repo.AddTarget(target, data.CanonicalTargetsRole) 544 } 545 546 // there are delegation roles, find every delegation role we have a key for, and 547 // attempt to sign into into all those roles. 548 for _, delegationRole := range allDelegationRoles { 549 // We do not support signing any delegation role that isn't a direct child of the targets role. 550 // Also don't bother checking the keys if we can't add the target 551 // to this role due to path restrictions 552 if path.Dir(delegationRole.Name) != data.CanonicalTargetsRole || !delegationRole.CheckPaths(target.Name) { 553 continue 554 } 555 556 for _, canonicalKeyID := range delegationRole.KeyIDs { 557 if _, ok := allCanonicalKeyIDs[canonicalKeyID]; ok { 558 signableRoles = append(signableRoles, delegationRole.Name) 559 break 560 } 561 } 562 } 563 564 if len(signableRoles) == 0 { 565 return fmt.Errorf("no valid signing keys for delegation roles") 566 } 567 568 return repo.AddTarget(target, signableRoles...) 569 } 570 571 // ImagePullPrivileged pulls the image and displays it to the output 572 func (cli *DockerCli) ImagePullPrivileged(ctx context.Context, authConfig types.AuthConfig, ref string, requestPrivilege types.RequestPrivilegeFunc, all bool) error { 573 574 encodedAuth, err := EncodeAuthToBase64(authConfig) 575 if err != nil { 576 return err 577 } 578 options := types.ImagePullOptions{ 579 RegistryAuth: encodedAuth, 580 PrivilegeFunc: requestPrivilege, 581 All: all, 582 } 583 584 responseBody, err := cli.client.ImagePull(ctx, ref, options) 585 if err != nil { 586 return err 587 } 588 defer responseBody.Close() 589 590 return jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut, nil) 591 } 592 593 // ImagePushPrivileged push the image 594 func (cli *DockerCli) ImagePushPrivileged(ctx context.Context, authConfig types.AuthConfig, ref string, requestPrivilege types.RequestPrivilegeFunc) (io.ReadCloser, error) { 595 encodedAuth, err := EncodeAuthToBase64(authConfig) 596 if err != nil { 597 return nil, err 598 } 599 options := types.ImagePushOptions{ 600 RegistryAuth: encodedAuth, 601 PrivilegeFunc: requestPrivilege, 602 } 603 604 return cli.client.ImagePush(ctx, ref, options) 605 }