github.com/rsampaio/docker@v0.7.2-0.20150827203920-fdc73cc3fc31/api/client/trust.go (about) 1 package client 2 3 import ( 4 "bufio" 5 "encoding/hex" 6 "encoding/json" 7 "errors" 8 "fmt" 9 "io" 10 "net" 11 "net/http" 12 "net/url" 13 "os" 14 "path/filepath" 15 "regexp" 16 "sort" 17 "strconv" 18 "strings" 19 "time" 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/pkg/ansiescape" 27 "github.com/docker/docker/pkg/ioutils" 28 flag "github.com/docker/docker/pkg/mflag" 29 "github.com/docker/docker/pkg/tlsconfig" 30 "github.com/docker/docker/registry" 31 "github.com/docker/notary/client" 32 "github.com/docker/notary/pkg/passphrase" 33 "github.com/docker/notary/trustmanager" 34 "github.com/endophage/gotuf/data" 35 ) 36 37 var untrusted bool 38 39 func addTrustedFlags(fs *flag.FlagSet, verify bool) { 40 var trusted bool 41 if e := os.Getenv("DOCKER_CONTENT_TRUST"); e != "" { 42 if t, err := strconv.ParseBool(e); t || err != nil { 43 // treat any other value as true 44 trusted = true 45 } 46 } 47 message := "Skip image signing" 48 if verify { 49 message = "Skip image verification" 50 } 51 fs.BoolVar(&untrusted, []string{"-disable-content-trust"}, !trusted, message) 52 } 53 54 func isTrusted() bool { 55 return !untrusted 56 } 57 58 var targetRegexp = regexp.MustCompile(`([\S]+): digest: ([\S]+) size: ([\d]+)`) 59 60 type target struct { 61 reference registry.Reference 62 digest digest.Digest 63 size int64 64 } 65 66 func (cli *DockerCli) trustDirectory() string { 67 return filepath.Join(cliconfig.ConfigDir(), "trust") 68 } 69 70 // certificateDirectory returns the directory containing 71 // TLS certificates for the given server. An error is 72 // returned if there was an error parsing the server string. 73 func (cli *DockerCli) certificateDirectory(server string) (string, error) { 74 u, err := url.Parse(server) 75 if err != nil { 76 return "", err 77 } 78 79 return filepath.Join(cliconfig.ConfigDir(), "tls", u.Host), nil 80 } 81 82 func trustServer(index *registry.IndexInfo) string { 83 if s := os.Getenv("DOCKER_CONTENT_TRUST_SERVER"); s != "" { 84 if !strings.HasPrefix(s, "https://") { 85 return "https://" + s 86 } 87 return s 88 } 89 if index.Official { 90 return registry.NotaryServer 91 } 92 return "https://" + index.Name 93 } 94 95 type simpleCredentialStore struct { 96 auth cliconfig.AuthConfig 97 } 98 99 func (scs simpleCredentialStore) Basic(u *url.URL) (string, string) { 100 return scs.auth.Username, scs.auth.Password 101 } 102 103 func (cli *DockerCli) getNotaryRepository(repoInfo *registry.RepositoryInfo, authConfig cliconfig.AuthConfig) (*client.NotaryRepository, error) { 104 server := trustServer(repoInfo.Index) 105 if !strings.HasPrefix(server, "https://") { 106 return nil, errors.New("unsupported scheme: https required for trust server") 107 } 108 109 var cfg = tlsconfig.ClientDefault 110 cfg.InsecureSkipVerify = !repoInfo.Index.Secure 111 112 // Get certificate base directory 113 certDir, err := cli.certificateDirectory(server) 114 if err != nil { 115 return nil, err 116 } 117 logrus.Debugf("reading certificate directory: %s", certDir) 118 119 if err := registry.ReadCertsDirectory(&cfg, certDir); err != nil { 120 return nil, err 121 } 122 123 base := &http.Transport{ 124 Proxy: http.ProxyFromEnvironment, 125 Dial: (&net.Dialer{ 126 Timeout: 30 * time.Second, 127 KeepAlive: 30 * time.Second, 128 DualStack: true, 129 }).Dial, 130 TLSHandshakeTimeout: 10 * time.Second, 131 TLSClientConfig: &cfg, 132 DisableKeepAlives: true, 133 } 134 135 // Skip configuration headers since request is not going to Docker daemon 136 modifiers := registry.DockerHeaders(http.Header{}) 137 authTransport := transport.NewTransport(base, modifiers...) 138 pingClient := &http.Client{ 139 Transport: authTransport, 140 Timeout: 5 * time.Second, 141 } 142 endpointStr := server + "/v2/" 143 req, err := http.NewRequest("GET", endpointStr, nil) 144 if err != nil { 145 return nil, err 146 } 147 resp, err := pingClient.Do(req) 148 if err != nil { 149 return nil, err 150 } 151 defer resp.Body.Close() 152 153 challengeManager := auth.NewSimpleChallengeManager() 154 if err := challengeManager.AddResponse(resp); err != nil { 155 return nil, err 156 } 157 158 creds := simpleCredentialStore{auth: authConfig} 159 tokenHandler := auth.NewTokenHandler(authTransport, creds, repoInfo.CanonicalName, "push", "pull") 160 basicHandler := auth.NewBasicHandler(creds) 161 modifiers = append(modifiers, transport.RequestModifier(auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler))) 162 tr := transport.NewTransport(base, modifiers...) 163 164 return client.NewNotaryRepository(cli.trustDirectory(), repoInfo.CanonicalName, server, tr, cli.getPassphraseRetriever()) 165 } 166 167 func convertTarget(t client.Target) (target, error) { 168 h, ok := t.Hashes["sha256"] 169 if !ok { 170 return target{}, errors.New("no valid hash, expecting sha256") 171 } 172 return target{ 173 reference: registry.ParseReference(t.Name), 174 digest: digest.NewDigestFromHex("sha256", hex.EncodeToString(h)), 175 size: t.Length, 176 }, nil 177 } 178 179 func (cli *DockerCli) getPassphraseRetriever() passphrase.Retriever { 180 aliasMap := map[string]string{ 181 "root": "offline", 182 "snapshot": "tagging", 183 "targets": "tagging", 184 } 185 baseRetriever := passphrase.PromptRetrieverWithInOut(cli.in, cli.out, aliasMap) 186 env := map[string]string{ 187 "root": os.Getenv("DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE"), 188 "snapshot": os.Getenv("DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE"), 189 "targets": os.Getenv("DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE"), 190 } 191 return func(keyName string, alias string, createNew bool, numAttempts int) (string, bool, error) { 192 if v := env[alias]; v != "" { 193 return v, numAttempts > 1, nil 194 } 195 return baseRetriever(keyName, alias, createNew, numAttempts) 196 } 197 } 198 199 func (cli *DockerCli) trustedReference(repo string, ref registry.Reference) (registry.Reference, error) { 200 repoInfo, err := registry.ParseRepositoryInfo(repo) 201 if err != nil { 202 return nil, err 203 } 204 205 // Resolve the Auth config relevant for this server 206 authConfig := registry.ResolveAuthConfig(cli.configFile, repoInfo.Index) 207 208 notaryRepo, err := cli.getNotaryRepository(repoInfo, authConfig) 209 if err != nil { 210 fmt.Fprintf(cli.out, "Error establishing connection to trust repository: %s\n", err) 211 return nil, err 212 } 213 214 t, err := notaryRepo.GetTargetByName(ref.String()) 215 if err != nil { 216 return nil, err 217 } 218 r, err := convertTarget(*t) 219 if err != nil { 220 return nil, err 221 222 } 223 224 return registry.DigestReference(r.digest), nil 225 } 226 227 func (cli *DockerCli) tagTrusted(repoInfo *registry.RepositoryInfo, trustedRef, ref registry.Reference) error { 228 fullName := trustedRef.ImageName(repoInfo.LocalName) 229 fmt.Fprintf(cli.out, "Tagging %s as %s\n", fullName, ref.ImageName(repoInfo.LocalName)) 230 tv := url.Values{} 231 tv.Set("repo", repoInfo.LocalName) 232 tv.Set("tag", ref.String()) 233 tv.Set("force", "1") 234 235 if _, _, err := readBody(cli.call("POST", "/images/"+fullName+"/tag?"+tv.Encode(), nil, nil)); err != nil { 236 return err 237 } 238 239 return nil 240 } 241 242 func notaryError(err error) error { 243 switch err.(type) { 244 case *json.SyntaxError: 245 logrus.Debugf("Notary syntax error: %s", err) 246 return errors.New("no trust data available for remote repository") 247 case client.ErrExpired: 248 return fmt.Errorf("remote repository out-of-date: %v", err) 249 case trustmanager.ErrKeyNotFound: 250 return fmt.Errorf("signing keys not found: %v", err) 251 } 252 253 return err 254 } 255 256 func (cli *DockerCli) trustedPull(repoInfo *registry.RepositoryInfo, ref registry.Reference, authConfig cliconfig.AuthConfig) error { 257 var ( 258 v = url.Values{} 259 refs = []target{} 260 ) 261 262 notaryRepo, err := cli.getNotaryRepository(repoInfo, authConfig) 263 if err != nil { 264 fmt.Fprintf(cli.out, "Error establishing connection to trust repository: %s\n", err) 265 return err 266 } 267 268 if ref.String() == "" { 269 // List all targets 270 targets, err := notaryRepo.ListTargets() 271 if err != nil { 272 return notaryError(err) 273 } 274 for _, tgt := range targets { 275 t, err := convertTarget(*tgt) 276 if err != nil { 277 fmt.Fprintf(cli.out, "Skipping target for %q\n", repoInfo.LocalName) 278 continue 279 } 280 refs = append(refs, t) 281 } 282 } else { 283 t, err := notaryRepo.GetTargetByName(ref.String()) 284 if err != nil { 285 return notaryError(err) 286 } 287 r, err := convertTarget(*t) 288 if err != nil { 289 return err 290 291 } 292 refs = append(refs, r) 293 } 294 295 v.Set("fromImage", repoInfo.LocalName) 296 for i, r := range refs { 297 displayTag := r.reference.String() 298 if displayTag != "" { 299 displayTag = ":" + displayTag 300 } 301 fmt.Fprintf(cli.out, "Pull (%d of %d): %s%s@%s\n", i+1, len(refs), repoInfo.LocalName, displayTag, r.digest) 302 v.Set("tag", r.digest.String()) 303 304 _, _, err = cli.clientRequestAttemptLogin("POST", "/images/create?"+v.Encode(), nil, cli.out, repoInfo.Index, "pull") 305 if err != nil { 306 return err 307 } 308 309 // If reference is not trusted, tag by trusted reference 310 if !r.reference.HasDigest() { 311 if err := cli.tagTrusted(repoInfo, registry.DigestReference(r.digest), r.reference); err != nil { 312 return err 313 314 } 315 } 316 } 317 return nil 318 } 319 320 func selectKey(keys map[string]string) string { 321 if len(keys) == 0 { 322 return "" 323 } 324 325 keyIDs := []string{} 326 for k := range keys { 327 keyIDs = append(keyIDs, k) 328 } 329 330 // TODO(dmcgowan): let user choose if multiple keys, now pick consistently 331 sort.Strings(keyIDs) 332 333 return keyIDs[0] 334 } 335 336 func targetStream(in io.Writer) (io.WriteCloser, <-chan []target) { 337 r, w := io.Pipe() 338 out := io.MultiWriter(in, w) 339 targetChan := make(chan []target) 340 341 go func() { 342 targets := []target{} 343 scanner := bufio.NewScanner(r) 344 scanner.Split(ansiescape.ScanANSILines) 345 for scanner.Scan() { 346 line := scanner.Bytes() 347 if matches := targetRegexp.FindSubmatch(line); len(matches) == 4 { 348 dgst, err := digest.ParseDigest(string(matches[2])) 349 if err != nil { 350 // Line does match what is expected, continue looking for valid lines 351 logrus.Debugf("Bad digest value %q in matched line, ignoring\n", string(matches[2])) 352 continue 353 } 354 s, err := strconv.ParseInt(string(matches[3]), 10, 64) 355 if err != nil { 356 // Line does match what is expected, continue looking for valid lines 357 logrus.Debugf("Bad size value %q in matched line, ignoring\n", string(matches[3])) 358 continue 359 } 360 361 targets = append(targets, target{ 362 reference: registry.ParseReference(string(matches[1])), 363 digest: dgst, 364 size: s, 365 }) 366 } 367 } 368 targetChan <- targets 369 }() 370 371 return ioutils.NewWriteCloserWrapper(out, w.Close), targetChan 372 } 373 374 func (cli *DockerCli) trustedPush(repoInfo *registry.RepositoryInfo, tag string, authConfig cliconfig.AuthConfig) error { 375 streamOut, targetChan := targetStream(cli.out) 376 377 v := url.Values{} 378 v.Set("tag", tag) 379 380 _, _, err := cli.clientRequestAttemptLogin("POST", "/images/"+repoInfo.LocalName+"/push?"+v.Encode(), nil, streamOut, repoInfo.Index, "push") 381 // Close stream channel to finish target parsing 382 if err := streamOut.Close(); err != nil { 383 return err 384 } 385 // Check error from request 386 if err != nil { 387 return err 388 } 389 390 // Get target results 391 targets := <-targetChan 392 393 if tag == "" { 394 fmt.Fprintf(cli.out, "No tag specified, skipping trust metadata push\n") 395 return nil 396 } 397 if len(targets) == 0 { 398 fmt.Fprintf(cli.out, "No targets found, skipping trust metadata push\n") 399 return nil 400 } 401 402 fmt.Fprintf(cli.out, "Signing and pushing trust metadata\n") 403 404 repo, err := cli.getNotaryRepository(repoInfo, authConfig) 405 if err != nil { 406 fmt.Fprintf(cli.out, "Error establishing connection to notary repository: %s\n", err) 407 return err 408 } 409 410 for _, target := range targets { 411 h, err := hex.DecodeString(target.digest.Hex()) 412 if err != nil { 413 return err 414 } 415 t := &client.Target{ 416 Name: target.reference.String(), 417 Hashes: data.Hashes{ 418 string(target.digest.Algorithm()): h, 419 }, 420 Length: int64(target.size), 421 } 422 if err := repo.AddTarget(t); err != nil { 423 return err 424 } 425 } 426 427 err = repo.Publish() 428 if _, ok := err.(*client.ErrRepoNotInitialized); !ok { 429 return notaryError(err) 430 } 431 432 ks := repo.KeyStoreManager 433 keys := ks.RootKeyStore().ListKeys() 434 435 rootKey := selectKey(keys) 436 if rootKey == "" { 437 rootKey, err = ks.GenRootKey("ecdsa") 438 if err != nil { 439 return err 440 } 441 } 442 443 cryptoService, err := ks.GetRootCryptoService(rootKey) 444 if err != nil { 445 return err 446 } 447 448 if err := repo.Initialize(cryptoService); err != nil { 449 return notaryError(err) 450 } 451 fmt.Fprintf(cli.out, "Finished initializing %q\n", repoInfo.CanonicalName) 452 453 return notaryError(repo.Publish()) 454 }