github.com/vincentwoo/docker@v0.7.3-0.20160116130405-82401a4b13c0/api/client/trust.go (about) 1 package client 2 3 import ( 4 "encoding/hex" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "net" 9 "net/http" 10 "net/url" 11 "os" 12 "path" 13 "path/filepath" 14 "sort" 15 "strconv" 16 "time" 17 18 "github.com/Sirupsen/logrus" 19 "github.com/docker/distribution/digest" 20 "github.com/docker/distribution/registry/client/auth" 21 "github.com/docker/distribution/registry/client/transport" 22 "github.com/docker/docker/cliconfig" 23 "github.com/docker/docker/distribution" 24 "github.com/docker/docker/pkg/jsonmessage" 25 flag "github.com/docker/docker/pkg/mflag" 26 "github.com/docker/docker/reference" 27 "github.com/docker/docker/registry" 28 apiclient "github.com/docker/engine-api/client" 29 "github.com/docker/engine-api/types" 30 registrytypes "github.com/docker/engine-api/types/registry" 31 "github.com/docker/go-connections/tlsconfig" 32 "github.com/docker/notary/client" 33 "github.com/docker/notary/passphrase" 34 "github.com/docker/notary/trustmanager" 35 "github.com/docker/notary/tuf/data" 36 "github.com/docker/notary/tuf/signed" 37 "github.com/docker/notary/tuf/store" 38 ) 39 40 var ( 41 releasesRole = path.Join(data.CanonicalTargetsRole, "releases") 42 untrusted bool 43 ) 44 45 func addTrustedFlags(fs *flag.FlagSet, verify bool) { 46 var trusted bool 47 if e := os.Getenv("DOCKER_CONTENT_TRUST"); e != "" { 48 if t, err := strconv.ParseBool(e); t || err != nil { 49 // treat any other value as true 50 trusted = true 51 } 52 } 53 message := "Skip image signing" 54 if verify { 55 message = "Skip image verification" 56 } 57 fs.BoolVar(&untrusted, []string{"-disable-content-trust"}, !trusted, message) 58 } 59 60 func isTrusted() bool { 61 return !untrusted 62 } 63 64 type target struct { 65 reference registry.Reference 66 digest digest.Digest 67 size int64 68 } 69 70 func (cli *DockerCli) trustDirectory() string { 71 return filepath.Join(cliconfig.ConfigDir(), "trust") 72 } 73 74 // certificateDirectory returns the directory containing 75 // TLS certificates for the given server. An error is 76 // returned if there was an error parsing the server string. 77 func (cli *DockerCli) certificateDirectory(server string) (string, error) { 78 u, err := url.Parse(server) 79 if err != nil { 80 return "", err 81 } 82 83 return filepath.Join(cliconfig.ConfigDir(), "tls", u.Host), nil 84 } 85 86 func trustServer(index *registrytypes.IndexInfo) (string, error) { 87 if s := os.Getenv("DOCKER_CONTENT_TRUST_SERVER"); s != "" { 88 urlObj, err := url.Parse(s) 89 if err != nil || urlObj.Scheme != "https" { 90 return "", fmt.Errorf("valid https URL required for trust server, got %s", s) 91 } 92 93 return s, nil 94 } 95 if index.Official { 96 return registry.NotaryServer, nil 97 } 98 return "https://" + index.Name, nil 99 } 100 101 type simpleCredentialStore struct { 102 auth types.AuthConfig 103 } 104 105 func (scs simpleCredentialStore) Basic(u *url.URL) (string, string) { 106 return scs.auth.Username, scs.auth.Password 107 } 108 109 func (cli *DockerCli) getNotaryRepository(repoInfo *registry.RepositoryInfo, authConfig types.AuthConfig) (*client.NotaryRepository, error) { 110 server, err := trustServer(repoInfo.Index) 111 if err != nil { 112 return nil, err 113 } 114 115 var cfg = tlsconfig.ClientDefault 116 cfg.InsecureSkipVerify = !repoInfo.Index.Secure 117 118 // Get certificate base directory 119 certDir, err := cli.certificateDirectory(server) 120 if err != nil { 121 return nil, err 122 } 123 logrus.Debugf("reading certificate directory: %s", certDir) 124 125 if err := registry.ReadCertsDirectory(&cfg, certDir); err != nil { 126 return nil, err 127 } 128 129 base := &http.Transport{ 130 Proxy: http.ProxyFromEnvironment, 131 Dial: (&net.Dialer{ 132 Timeout: 30 * time.Second, 133 KeepAlive: 30 * time.Second, 134 DualStack: true, 135 }).Dial, 136 TLSHandshakeTimeout: 10 * time.Second, 137 TLSClientConfig: &cfg, 138 DisableKeepAlives: true, 139 } 140 141 // Skip configuration headers since request is not going to Docker daemon 142 modifiers := registry.DockerHeaders(http.Header{}) 143 authTransport := transport.NewTransport(base, modifiers...) 144 pingClient := &http.Client{ 145 Transport: authTransport, 146 Timeout: 5 * time.Second, 147 } 148 endpointStr := server + "/v2/" 149 req, err := http.NewRequest("GET", endpointStr, nil) 150 if err != nil { 151 return nil, err 152 } 153 154 challengeManager := auth.NewSimpleChallengeManager() 155 156 resp, err := pingClient.Do(req) 157 if err != nil { 158 // Ignore error on ping to operate in offline mode 159 logrus.Debugf("Error pinging notary server %q: %s", endpointStr, err) 160 } else { 161 defer resp.Body.Close() 162 163 // Add response to the challenge manager to parse out 164 // authentication header and register authentication method 165 if err := challengeManager.AddResponse(resp); err != nil { 166 return nil, err 167 } 168 } 169 170 creds := simpleCredentialStore{auth: authConfig} 171 tokenHandler := auth.NewTokenHandler(authTransport, creds, repoInfo.FullName(), "push", "pull") 172 basicHandler := auth.NewBasicHandler(creds) 173 modifiers = append(modifiers, transport.RequestModifier(auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler))) 174 tr := transport.NewTransport(base, modifiers...) 175 176 return client.NewNotaryRepository(cli.trustDirectory(), repoInfo.FullName(), server, tr, cli.getPassphraseRetriever()) 177 } 178 179 func convertTarget(t client.Target) (target, error) { 180 h, ok := t.Hashes["sha256"] 181 if !ok { 182 return target{}, errors.New("no valid hash, expecting sha256") 183 } 184 return target{ 185 reference: registry.ParseReference(t.Name), 186 digest: digest.NewDigestFromHex("sha256", hex.EncodeToString(h)), 187 size: t.Length, 188 }, nil 189 } 190 191 func (cli *DockerCli) getPassphraseRetriever() passphrase.Retriever { 192 aliasMap := map[string]string{ 193 "root": "root", 194 "snapshot": "repository", 195 "targets": "repository", 196 "targets/releases": "repository", 197 } 198 baseRetriever := passphrase.PromptRetrieverWithInOut(cli.in, cli.out, aliasMap) 199 env := map[string]string{ 200 "root": os.Getenv("DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE"), 201 "snapshot": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"), 202 "targets": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"), 203 "targets/releases": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"), 204 } 205 206 // Backwards compatibility with old env names. We should remove this in 1.10 207 if env["root"] == "" { 208 if passphrase := os.Getenv("DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE"); passphrase != "" { 209 env["root"] = passphrase 210 fmt.Fprintf(cli.err, "[DEPRECATED] The environment variable DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE has been deprecated and will be removed in v1.10. Please use DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE\n") 211 } 212 } 213 if env["snapshot"] == "" || env["targets"] == "" || env["targets/releases"] == "" { 214 if passphrase := os.Getenv("DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE"); passphrase != "" { 215 env["snapshot"] = passphrase 216 env["targets"] = passphrase 217 env["targets/releases"] = passphrase 218 fmt.Fprintf(cli.err, "[DEPRECATED] The environment variable DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE has been deprecated and will be removed in v1.10. Please use DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE\n") 219 } 220 } 221 222 return func(keyName string, alias string, createNew bool, numAttempts int) (string, bool, error) { 223 if v := env[alias]; v != "" { 224 return v, numAttempts > 1, nil 225 } 226 return baseRetriever(keyName, alias, createNew, numAttempts) 227 } 228 } 229 230 func (cli *DockerCli) trustedReference(ref reference.NamedTagged) (reference.Canonical, error) { 231 repoInfo, err := registry.ParseRepositoryInfo(ref) 232 if err != nil { 233 return nil, err 234 } 235 236 // Resolve the Auth config relevant for this server 237 authConfig := registry.ResolveAuthConfig(cli.configFile.AuthConfigs, repoInfo.Index) 238 239 notaryRepo, err := cli.getNotaryRepository(repoInfo, authConfig) 240 if err != nil { 241 fmt.Fprintf(cli.out, "Error establishing connection to trust repository: %s\n", err) 242 return nil, err 243 } 244 245 t, err := notaryRepo.GetTargetByName(ref.Tag(), releasesRole, data.CanonicalTargetsRole) 246 if err != nil { 247 return nil, err 248 } 249 r, err := convertTarget(t.Target) 250 if err != nil { 251 return nil, err 252 253 } 254 255 return reference.WithDigest(ref, r.digest) 256 } 257 258 func (cli *DockerCli) tagTrusted(trustedRef reference.Canonical, ref reference.NamedTagged) error { 259 fmt.Fprintf(cli.out, "Tagging %s as %s\n", trustedRef.String(), ref.String()) 260 261 options := types.ImageTagOptions{ 262 ImageID: trustedRef.String(), 263 RepositoryName: trustedRef.Name(), 264 Tag: ref.Tag(), 265 Force: true, 266 } 267 268 return cli.client.ImageTag(options) 269 } 270 271 func notaryError(repoName string, err error) error { 272 switch err.(type) { 273 case *json.SyntaxError: 274 logrus.Debugf("Notary syntax error: %s", err) 275 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) 276 case signed.ErrExpired: 277 return fmt.Errorf("Error: remote repository %s out-of-date: %v", repoName, err) 278 case trustmanager.ErrKeyNotFound: 279 return fmt.Errorf("Error: signing keys for remote repository %s not found: %v", repoName, err) 280 case *net.OpError: 281 return fmt.Errorf("Error: error contacting notary server: %v", err) 282 case store.ErrMetaNotFound: 283 return fmt.Errorf("Error: trust data missing for remote repository %s or remote repository not found: %v", repoName, err) 284 case signed.ErrInvalidKeyType: 285 return fmt.Errorf("Warning: potential malicious behavior - trust data mismatch for remote repository %s: %v", repoName, err) 286 case signed.ErrNoKeys: 287 return fmt.Errorf("Error: could not find signing keys for remote repository %s, or could not decrypt signing key: %v", repoName, err) 288 case signed.ErrLowVersion: 289 return fmt.Errorf("Warning: potential malicious behavior - trust data version is lower than expected for remote repository %s: %v", repoName, err) 290 case signed.ErrRoleThreshold: 291 return fmt.Errorf("Warning: potential malicious behavior - trust data has insufficient signatures for remote repository %s: %v", repoName, err) 292 case client.ErrRepositoryNotExist: 293 return fmt.Errorf("Error: remote trust data does not exist for %s: %v", repoName, err) 294 case signed.ErrInsufficientSignatures: 295 return fmt.Errorf("Error: could not produce valid signature for %s. If Yubikey was used, was touch input provided?: %v", repoName, err) 296 } 297 298 return err 299 } 300 301 func (cli *DockerCli) trustedPull(repoInfo *registry.RepositoryInfo, ref registry.Reference, authConfig types.AuthConfig, requestPrivilege apiclient.RequestPrivilegeFunc) error { 302 var refs []target 303 304 notaryRepo, err := cli.getNotaryRepository(repoInfo, authConfig) 305 if err != nil { 306 fmt.Fprintf(cli.out, "Error establishing connection to trust repository: %s\n", err) 307 return err 308 } 309 310 if ref.String() == "" { 311 // List all targets 312 targets, err := notaryRepo.ListTargets(releasesRole, data.CanonicalTargetsRole) 313 if err != nil { 314 return notaryError(repoInfo.FullName(), err) 315 } 316 for _, tgt := range targets { 317 t, err := convertTarget(tgt.Target) 318 if err != nil { 319 fmt.Fprintf(cli.out, "Skipping target for %q\n", repoInfo.Name()) 320 continue 321 } 322 refs = append(refs, t) 323 } 324 } else { 325 t, err := notaryRepo.GetTargetByName(ref.String(), releasesRole, data.CanonicalTargetsRole) 326 if err != nil { 327 return notaryError(repoInfo.FullName(), err) 328 } 329 r, err := convertTarget(t.Target) 330 if err != nil { 331 return err 332 333 } 334 refs = append(refs, r) 335 } 336 337 for i, r := range refs { 338 displayTag := r.reference.String() 339 if displayTag != "" { 340 displayTag = ":" + displayTag 341 } 342 fmt.Fprintf(cli.out, "Pull (%d of %d): %s%s@%s\n", i+1, len(refs), repoInfo.Name(), displayTag, r.digest) 343 344 if err := cli.imagePullPrivileged(authConfig, repoInfo.Name(), r.digest.String(), requestPrivilege); err != nil { 345 return err 346 } 347 348 // If reference is not trusted, tag by trusted reference 349 if !r.reference.HasDigest() { 350 tagged, err := reference.WithTag(repoInfo, r.reference.String()) 351 if err != nil { 352 return err 353 } 354 trustedRef, err := reference.WithDigest(repoInfo, r.digest) 355 if err != nil { 356 return err 357 } 358 if err := cli.tagTrusted(trustedRef, tagged); err != nil { 359 return err 360 } 361 } 362 } 363 return nil 364 } 365 366 func (cli *DockerCli) trustedPush(repoInfo *registry.RepositoryInfo, tag string, authConfig types.AuthConfig, requestPrivilege apiclient.RequestPrivilegeFunc) error { 367 responseBody, err := cli.imagePushPrivileged(authConfig, repoInfo.Name(), tag, requestPrivilege) 368 if err != nil { 369 return err 370 } 371 372 defer responseBody.Close() 373 374 targets := []target{} 375 handleTarget := func(aux *json.RawMessage) { 376 var pushResult distribution.PushResult 377 err := json.Unmarshal(*aux, &pushResult) 378 if err == nil && pushResult.Tag != "" && pushResult.Digest.Validate() == nil { 379 targets = append(targets, target{ 380 reference: registry.ParseReference(pushResult.Tag), 381 digest: pushResult.Digest, 382 size: int64(pushResult.Size), 383 }) 384 } 385 } 386 387 err = jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut, handleTarget) 388 if err != nil { 389 return err 390 } 391 392 if tag == "" { 393 fmt.Fprintf(cli.out, "No tag specified, skipping trust metadata push\n") 394 return nil 395 } 396 if len(targets) == 0 { 397 fmt.Fprintf(cli.out, "No targets found, skipping trust metadata push\n") 398 return nil 399 } 400 401 fmt.Fprintf(cli.out, "Signing and pushing trust metadata\n") 402 403 repo, err := cli.getNotaryRepository(repoInfo, authConfig) 404 if err != nil { 405 fmt.Fprintf(cli.out, "Error establishing connection to notary repository: %s\n", err) 406 return err 407 } 408 409 for _, target := range targets { 410 h, err := hex.DecodeString(target.digest.Hex()) 411 if err != nil { 412 return err 413 } 414 t := &client.Target{ 415 Name: target.reference.String(), 416 Hashes: data.Hashes{ 417 string(target.digest.Algorithm()): h, 418 }, 419 Length: int64(target.size), 420 } 421 if err := repo.AddTarget(t, releasesRole); err != nil { 422 return err 423 } 424 } 425 426 err = repo.Publish() 427 if _, ok := err.(client.ErrRepoNotInitialized); !ok { 428 return notaryError(repoInfo.FullName(), err) 429 } 430 431 keys := repo.CryptoService.ListKeys(data.CanonicalRootRole) 432 433 var rootKeyID string 434 // always select the first root key 435 if len(keys) > 0 { 436 sort.Strings(keys) 437 rootKeyID = keys[0] 438 } else { 439 rootPublicKey, err := repo.CryptoService.Create(data.CanonicalRootRole, data.ECDSAKey) 440 if err != nil { 441 return err 442 } 443 rootKeyID = rootPublicKey.ID() 444 } 445 446 if err := repo.Initialize(rootKeyID); err != nil { 447 return notaryError(repoInfo.FullName(), err) 448 } 449 fmt.Fprintf(cli.out, "Finished initializing %q\n", repoInfo.FullName()) 450 451 return notaryError(repoInfo.FullName(), repo.Publish()) 452 }