Browse code

cli: Split out GetNotaryRepository and associated functions

Split these into cli/trust so that other commands can make use of them.

Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
(cherry picked from commit 4b8c79f25ee00ca5dfe22271c166938009bda976)

Aaron Lehmann authored on 2016/12/06 09:06:29
Showing 3 changed files
... ...
@@ -6,43 +6,22 @@ import (
6 6
 	"errors"
7 7
 	"fmt"
8 8
 	"io"
9
-	"net"
10
-	"net/http"
11
-	"net/url"
12
-	"os"
13 9
 	"path"
14
-	"path/filepath"
15 10
 	"sort"
16
-	"time"
17 11
 
18 12
 	"golang.org/x/net/context"
19 13
 
20 14
 	"github.com/Sirupsen/logrus"
21 15
 	"github.com/docker/distribution/digest"
22
-	"github.com/docker/distribution/registry/client/auth"
23
-	"github.com/docker/distribution/registry/client/auth/challenge"
24
-	"github.com/docker/distribution/registry/client/transport"
25 16
 	"github.com/docker/docker/api/types"
26
-	registrytypes "github.com/docker/docker/api/types/registry"
27 17
 	"github.com/docker/docker/cli/command"
28
-	"github.com/docker/docker/cliconfig"
18
+	"github.com/docker/docker/cli/trust"
29 19
 	"github.com/docker/docker/distribution"
30 20
 	"github.com/docker/docker/pkg/jsonmessage"
31 21
 	"github.com/docker/docker/reference"
32 22
 	"github.com/docker/docker/registry"
33
-	"github.com/docker/go-connections/tlsconfig"
34
-	"github.com/docker/notary"
35 23
 	"github.com/docker/notary/client"
36
-	"github.com/docker/notary/passphrase"
37
-	"github.com/docker/notary/storage"
38
-	"github.com/docker/notary/trustmanager"
39
-	"github.com/docker/notary/trustpinning"
40 24
 	"github.com/docker/notary/tuf/data"
41
-	"github.com/docker/notary/tuf/signed"
42
-)
43
-
44
-var (
45
-	releasesRole = path.Join(data.CanonicalTargetsRole, "releases")
46 25
 )
47 26
 
48 27
 type target struct {
... ...
@@ -118,7 +97,7 @@ func trustedPush(ctx context.Context, cli *command.DockerCli, repoInfo *registry
118 118
 
119 119
 	fmt.Fprintln(cli.Out(), "Signing and pushing trust metadata")
120 120
 
121
-	repo, err := GetNotaryRepository(cli, repoInfo, authConfig, "push", "pull")
121
+	repo, err := trust.GetNotaryRepository(cli, repoInfo, authConfig, "push", "pull")
122 122
 	if err != nil {
123 123
 		fmt.Fprintf(cli.Out(), "Error establishing connection to notary repository: %s\n", err)
124 124
 		return err
... ...
@@ -145,7 +124,7 @@ func trustedPush(ctx context.Context, cli *command.DockerCli, repoInfo *registry
145 145
 
146 146
 		// Initialize the notary repository with a remotely managed snapshot key
147 147
 		if err := repo.Initialize([]string{rootKeyID}, data.CanonicalSnapshotRole); err != nil {
148
-			return notaryError(repoInfo.FullName(), err)
148
+			return trust.NotaryError(repoInfo.FullName(), err)
149 149
 		}
150 150
 		fmt.Fprintf(cli.Out(), "Finished initializing %q\n", repoInfo.FullName())
151 151
 		err = repo.AddTarget(target, data.CanonicalTargetsRole)
... ...
@@ -153,7 +132,7 @@ func trustedPush(ctx context.Context, cli *command.DockerCli, repoInfo *registry
153 153
 		// already initialized and we have successfully downloaded the latest metadata
154 154
 		err = addTargetToAllSignableRoles(repo, target)
155 155
 	default:
156
-		return notaryError(repoInfo.FullName(), err)
156
+		return trust.NotaryError(repoInfo.FullName(), err)
157 157
 	}
158 158
 
159 159
 	if err == nil {
... ...
@@ -162,7 +141,7 @@ func trustedPush(ctx context.Context, cli *command.DockerCli, repoInfo *registry
162 162
 
163 163
 	if err != nil {
164 164
 		fmt.Fprintf(cli.Out(), "Failed to sign %q:%s - %s\n", repoInfo.FullName(), tag, err.Error())
165
-		return notaryError(repoInfo.FullName(), err)
165
+		return trust.NotaryError(repoInfo.FullName(), err)
166 166
 	}
167 167
 
168 168
 	fmt.Fprintf(cli.Out(), "Successfully signed %q:%s\n", repoInfo.FullName(), tag)
... ...
@@ -235,7 +214,7 @@ func imagePushPrivileged(ctx context.Context, cli *command.DockerCli, authConfig
235 235
 func trustedPull(ctx context.Context, cli *command.DockerCli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error {
236 236
 	var refs []target
237 237
 
238
-	notaryRepo, err := GetNotaryRepository(cli, repoInfo, authConfig, "pull")
238
+	notaryRepo, err := trust.GetNotaryRepository(cli, repoInfo, authConfig, "pull")
239 239
 	if err != nil {
240 240
 		fmt.Fprintf(cli.Out(), "Error establishing connection to trust repository: %s\n", err)
241 241
 		return err
... ...
@@ -243,9 +222,9 @@ func trustedPull(ctx context.Context, cli *command.DockerCli, repoInfo *registry
243 243
 
244 244
 	if tagged, isTagged := ref.(reference.NamedTagged); !isTagged {
245 245
 		// List all targets
246
-		targets, err := notaryRepo.ListTargets(releasesRole, data.CanonicalTargetsRole)
246
+		targets, err := notaryRepo.ListTargets(trust.ReleasesRole, data.CanonicalTargetsRole)
247 247
 		if err != nil {
248
-			return notaryError(repoInfo.FullName(), err)
248
+			return trust.NotaryError(repoInfo.FullName(), err)
249 249
 		}
250 250
 		for _, tgt := range targets {
251 251
 			t, err := convertTarget(tgt.Target)
... ...
@@ -255,23 +234,23 @@ func trustedPull(ctx context.Context, cli *command.DockerCli, repoInfo *registry
255 255
 			}
256 256
 			// Only list tags in the top level targets role or the releases delegation role - ignore
257 257
 			// all other delegation roles
258
-			if tgt.Role != releasesRole && tgt.Role != data.CanonicalTargetsRole {
258
+			if tgt.Role != trust.ReleasesRole && tgt.Role != data.CanonicalTargetsRole {
259 259
 				continue
260 260
 			}
261 261
 			refs = append(refs, t)
262 262
 		}
263 263
 		if len(refs) == 0 {
264
-			return notaryError(repoInfo.FullName(), fmt.Errorf("No trusted tags for %s", repoInfo.FullName()))
264
+			return trust.NotaryError(repoInfo.FullName(), fmt.Errorf("No trusted tags for %s", repoInfo.FullName()))
265 265
 		}
266 266
 	} else {
267
-		t, err := notaryRepo.GetTargetByName(tagged.Tag(), releasesRole, data.CanonicalTargetsRole)
267
+		t, err := notaryRepo.GetTargetByName(tagged.Tag(), trust.ReleasesRole, data.CanonicalTargetsRole)
268 268
 		if err != nil {
269
-			return notaryError(repoInfo.FullName(), err)
269
+			return trust.NotaryError(repoInfo.FullName(), err)
270 270
 		}
271 271
 		// Only get the tag if it's in the top level targets role or the releases delegation role
272 272
 		// ignore it if it's in any other delegation roles
273
-		if t.Role != releasesRole && t.Role != data.CanonicalTargetsRole {
274
-			return notaryError(repoInfo.FullName(), fmt.Errorf("No trust data for %s", tagged.Tag()))
273
+		if t.Role != trust.ReleasesRole && t.Role != data.CanonicalTargetsRole {
274
+			return trust.NotaryError(repoInfo.FullName(), fmt.Errorf("No trust data for %s", tagged.Tag()))
275 275
 		}
276 276
 
277 277
 		logrus.Debugf("retrieving target for %s role\n", t.Role)
... ...
@@ -335,159 +314,6 @@ func imagePullPrivileged(ctx context.Context, cli *command.DockerCli, authConfig
335 335
 	return jsonmessage.DisplayJSONMessagesToStream(responseBody, cli.Out(), nil)
336 336
 }
337 337
 
338
-func trustDirectory() string {
339
-	return filepath.Join(cliconfig.ConfigDir(), "trust")
340
-}
341
-
342
-// certificateDirectory returns the directory containing
343
-// TLS certificates for the given server. An error is
344
-// returned if there was an error parsing the server string.
345
-func certificateDirectory(server string) (string, error) {
346
-	u, err := url.Parse(server)
347
-	if err != nil {
348
-		return "", err
349
-	}
350
-
351
-	return filepath.Join(cliconfig.ConfigDir(), "tls", u.Host), nil
352
-}
353
-
354
-func trustServer(index *registrytypes.IndexInfo) (string, error) {
355
-	if s := os.Getenv("DOCKER_CONTENT_TRUST_SERVER"); s != "" {
356
-		urlObj, err := url.Parse(s)
357
-		if err != nil || urlObj.Scheme != "https" {
358
-			return "", fmt.Errorf("valid https URL required for trust server, got %s", s)
359
-		}
360
-
361
-		return s, nil
362
-	}
363
-	if index.Official {
364
-		return registry.NotaryServer, nil
365
-	}
366
-	return "https://" + index.Name, nil
367
-}
368
-
369
-type simpleCredentialStore struct {
370
-	auth types.AuthConfig
371
-}
372
-
373
-func (scs simpleCredentialStore) Basic(u *url.URL) (string, string) {
374
-	return scs.auth.Username, scs.auth.Password
375
-}
376
-
377
-func (scs simpleCredentialStore) RefreshToken(u *url.URL, service string) string {
378
-	return scs.auth.IdentityToken
379
-}
380
-
381
-func (scs simpleCredentialStore) SetRefreshToken(*url.URL, string, string) {
382
-}
383
-
384
-// GetNotaryRepository returns a NotaryRepository which stores all the
385
-// information needed to operate on a notary repository.
386
-// It creates an HTTP transport providing authentication support.
387
-// TODO: move this too
388
-func GetNotaryRepository(streams command.Streams, repoInfo *registry.RepositoryInfo, authConfig types.AuthConfig, actions ...string) (*client.NotaryRepository, error) {
389
-	server, err := trustServer(repoInfo.Index)
390
-	if err != nil {
391
-		return nil, err
392
-	}
393
-
394
-	var cfg = tlsconfig.ClientDefault()
395
-	cfg.InsecureSkipVerify = !repoInfo.Index.Secure
396
-
397
-	// Get certificate base directory
398
-	certDir, err := certificateDirectory(server)
399
-	if err != nil {
400
-		return nil, err
401
-	}
402
-	logrus.Debugf("reading certificate directory: %s", certDir)
403
-
404
-	if err := registry.ReadCertsDirectory(cfg, certDir); err != nil {
405
-		return nil, err
406
-	}
407
-
408
-	base := &http.Transport{
409
-		Proxy: http.ProxyFromEnvironment,
410
-		Dial: (&net.Dialer{
411
-			Timeout:   30 * time.Second,
412
-			KeepAlive: 30 * time.Second,
413
-			DualStack: true,
414
-		}).Dial,
415
-		TLSHandshakeTimeout: 10 * time.Second,
416
-		TLSClientConfig:     cfg,
417
-		DisableKeepAlives:   true,
418
-	}
419
-
420
-	// Skip configuration headers since request is not going to Docker daemon
421
-	modifiers := registry.DockerHeaders(command.UserAgent(), http.Header{})
422
-	authTransport := transport.NewTransport(base, modifiers...)
423
-	pingClient := &http.Client{
424
-		Transport: authTransport,
425
-		Timeout:   5 * time.Second,
426
-	}
427
-	endpointStr := server + "/v2/"
428
-	req, err := http.NewRequest("GET", endpointStr, nil)
429
-	if err != nil {
430
-		return nil, err
431
-	}
432
-
433
-	challengeManager := challenge.NewSimpleManager()
434
-
435
-	resp, err := pingClient.Do(req)
436
-	if err != nil {
437
-		// Ignore error on ping to operate in offline mode
438
-		logrus.Debugf("Error pinging notary server %q: %s", endpointStr, err)
439
-	} else {
440
-		defer resp.Body.Close()
441
-
442
-		// Add response to the challenge manager to parse out
443
-		// authentication header and register authentication method
444
-		if err := challengeManager.AddResponse(resp); err != nil {
445
-			return nil, err
446
-		}
447
-	}
448
-
449
-	creds := simpleCredentialStore{auth: authConfig}
450
-	tokenHandler := auth.NewTokenHandler(authTransport, creds, repoInfo.FullName(), actions...)
451
-	basicHandler := auth.NewBasicHandler(creds)
452
-	modifiers = append(modifiers, transport.RequestModifier(auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)))
453
-	tr := transport.NewTransport(base, modifiers...)
454
-
455
-	return client.NewNotaryRepository(
456
-		trustDirectory(),
457
-		repoInfo.FullName(),
458
-		server,
459
-		tr,
460
-		getPassphraseRetriever(streams),
461
-		trustpinning.TrustPinConfig{})
462
-}
463
-
464
-func getPassphraseRetriever(streams command.Streams) notary.PassRetriever {
465
-	aliasMap := map[string]string{
466
-		"root":     "root",
467
-		"snapshot": "repository",
468
-		"targets":  "repository",
469
-		"default":  "repository",
470
-	}
471
-	baseRetriever := passphrase.PromptRetrieverWithInOut(streams.In(), streams.Out(), aliasMap)
472
-	env := map[string]string{
473
-		"root":     os.Getenv("DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE"),
474
-		"snapshot": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
475
-		"targets":  os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
476
-		"default":  os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
477
-	}
478
-
479
-	return func(keyName string, alias string, createNew bool, numAttempts int) (string, bool, error) {
480
-		if v := env[alias]; v != "" {
481
-			return v, numAttempts > 1, nil
482
-		}
483
-		// For non-root roles, we can also try the "default" alias if it is specified
484
-		if v := env["default"]; v != "" && alias != data.CanonicalRootRole {
485
-			return v, numAttempts > 1, nil
486
-		}
487
-		return baseRetriever(keyName, alias, createNew, numAttempts)
488
-	}
489
-}
490
-
491 338
 // TrustedReference returns the canonical trusted reference for an image reference
492 339
 func TrustedReference(ctx context.Context, cli *command.DockerCli, ref reference.NamedTagged) (reference.Canonical, error) {
493 340
 	repoInfo, err := registry.ParseRepositoryInfo(ref)
... ...
@@ -498,20 +324,20 @@ func TrustedReference(ctx context.Context, cli *command.DockerCli, ref reference
498 498
 	// Resolve the Auth config relevant for this server
499 499
 	authConfig := command.ResolveAuthConfig(ctx, cli, repoInfo.Index)
500 500
 
501
-	notaryRepo, err := GetNotaryRepository(cli, repoInfo, authConfig, "pull")
501
+	notaryRepo, err := trust.GetNotaryRepository(cli, repoInfo, authConfig, "pull")
502 502
 	if err != nil {
503 503
 		fmt.Fprintf(cli.Out(), "Error establishing connection to trust repository: %s\n", err)
504 504
 		return nil, err
505 505
 	}
506 506
 
507
-	t, err := notaryRepo.GetTargetByName(ref.Tag(), releasesRole, data.CanonicalTargetsRole)
507
+	t, err := notaryRepo.GetTargetByName(ref.Tag(), trust.ReleasesRole, data.CanonicalTargetsRole)
508 508
 	if err != nil {
509 509
 		return nil, err
510 510
 	}
511 511
 	// Only list tags in the top level targets role or the releases delegation role - ignore
512 512
 	// all other delegation roles
513
-	if t.Role != releasesRole && t.Role != data.CanonicalTargetsRole {
514
-		return nil, notaryError(repoInfo.FullName(), fmt.Errorf("No trust data for %s", ref.Tag()))
513
+	if t.Role != trust.ReleasesRole && t.Role != data.CanonicalTargetsRole {
514
+		return nil, trust.NotaryError(repoInfo.FullName(), fmt.Errorf("No trust data for %s", ref.Tag()))
515 515
 	}
516 516
 	r, err := convertTarget(t.Target)
517 517
 	if err != nil {
... ...
@@ -540,34 +366,3 @@ func TagTrusted(ctx context.Context, cli *command.DockerCli, trustedRef referenc
540 540
 
541 541
 	return cli.Client().ImageTag(ctx, trustedRef.String(), ref.String())
542 542
 }
543
-
544
-// notaryError formats an error message received from the notary service
545
-func notaryError(repoName string, err error) error {
546
-	switch err.(type) {
547
-	case *json.SyntaxError:
548
-		logrus.Debugf("Notary syntax error: %s", err)
549
-		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)
550
-	case signed.ErrExpired:
551
-		return fmt.Errorf("Error: remote repository %s out-of-date: %v", repoName, err)
552
-	case trustmanager.ErrKeyNotFound:
553
-		return fmt.Errorf("Error: signing keys for remote repository %s not found: %v", repoName, err)
554
-	case storage.NetworkError:
555
-		return fmt.Errorf("Error: error contacting notary server: %v", err)
556
-	case storage.ErrMetaNotFound:
557
-		return fmt.Errorf("Error: trust data missing for remote repository %s or remote repository not found: %v", repoName, err)
558
-	case trustpinning.ErrRootRotationFail, trustpinning.ErrValidationFail, signed.ErrInvalidKeyType:
559
-		return fmt.Errorf("Warning: potential malicious behavior - trust data mismatch for remote repository %s: %v", repoName, err)
560
-	case signed.ErrNoKeys:
561
-		return fmt.Errorf("Error: could not find signing keys for remote repository %s, or could not decrypt signing key: %v", repoName, err)
562
-	case signed.ErrLowVersion:
563
-		return fmt.Errorf("Warning: potential malicious behavior - trust data version is lower than expected for remote repository %s: %v", repoName, err)
564
-	case signed.ErrRoleThreshold:
565
-		return fmt.Errorf("Warning: potential malicious behavior - trust data has insufficient signatures for remote repository %s: %v", repoName, err)
566
-	case client.ErrRepositoryNotExist:
567
-		return fmt.Errorf("Error: remote trust data does not exist for %s: %v", repoName, err)
568
-	case signed.ErrInsufficientSignatures:
569
-		return fmt.Errorf("Error: could not produce valid signature for %s.  If Yubikey was used, was touch input provided?: %v", repoName, err)
570
-	}
571
-
572
-	return err
573
-}
... ...
@@ -5,6 +5,7 @@ import (
5 5
 	"testing"
6 6
 
7 7
 	registrytypes "github.com/docker/docker/api/types/registry"
8
+	"github.com/docker/docker/cli/trust"
8 9
 	"github.com/docker/docker/registry"
9 10
 )
10 11
 
... ...
@@ -19,7 +20,7 @@ func TestENVTrustServer(t *testing.T) {
19 19
 	if err := os.Setenv("DOCKER_CONTENT_TRUST_SERVER", "https://notary-test.com:5000"); err != nil {
20 20
 		t.Fatal("Failed to set ENV variable")
21 21
 	}
22
-	output, err := trustServer(indexInfo)
22
+	output, err := trust.Server(indexInfo)
23 23
 	expectedStr := "https://notary-test.com:5000"
24 24
 	if err != nil || output != expectedStr {
25 25
 		t.Fatalf("Expected server to be %s, got %s", expectedStr, output)
... ...
@@ -32,7 +33,7 @@ func TestHTTPENVTrustServer(t *testing.T) {
32 32
 	if err := os.Setenv("DOCKER_CONTENT_TRUST_SERVER", "http://notary-test.com:5000"); err != nil {
33 33
 		t.Fatal("Failed to set ENV variable")
34 34
 	}
35
-	_, err := trustServer(indexInfo)
35
+	_, err := trust.Server(indexInfo)
36 36
 	if err == nil {
37 37
 		t.Fatal("Expected error with invalid scheme")
38 38
 	}
... ...
@@ -40,7 +41,7 @@ func TestHTTPENVTrustServer(t *testing.T) {
40 40
 
41 41
 func TestOfficialTrustServer(t *testing.T) {
42 42
 	indexInfo := &registrytypes.IndexInfo{Name: "testserver", Official: true}
43
-	output, err := trustServer(indexInfo)
43
+	output, err := trust.Server(indexInfo)
44 44
 	if err != nil || output != registry.NotaryServer {
45 45
 		t.Fatalf("Expected server to be %s, got %s", registry.NotaryServer, output)
46 46
 	}
... ...
@@ -48,7 +49,7 @@ func TestOfficialTrustServer(t *testing.T) {
48 48
 
49 49
 func TestNonOfficialTrustServer(t *testing.T) {
50 50
 	indexInfo := &registrytypes.IndexInfo{Name: "testserver", Official: false}
51
-	output, err := trustServer(indexInfo)
51
+	output, err := trust.Server(indexInfo)
52 52
 	expectedStr := "https://" + indexInfo.Name
53 53
 	if err != nil || output != expectedStr {
54 54
 		t.Fatalf("Expected server to be %s, got %s", expectedStr, output)
55 55
new file mode 100644
... ...
@@ -0,0 +1,221 @@
0
+package trust
1
+
2
+import (
3
+	"encoding/json"
4
+	"fmt"
5
+	"net"
6
+	"net/http"
7
+	"net/url"
8
+	"os"
9
+	"path"
10
+	"path/filepath"
11
+	"time"
12
+
13
+	"github.com/Sirupsen/logrus"
14
+	"github.com/docker/distribution/registry/client/auth"
15
+	"github.com/docker/distribution/registry/client/auth/challenge"
16
+	"github.com/docker/distribution/registry/client/transport"
17
+	"github.com/docker/docker/api/types"
18
+	registrytypes "github.com/docker/docker/api/types/registry"
19
+	"github.com/docker/docker/cli/command"
20
+	"github.com/docker/docker/cliconfig"
21
+	"github.com/docker/docker/registry"
22
+	"github.com/docker/go-connections/tlsconfig"
23
+	"github.com/docker/notary"
24
+	"github.com/docker/notary/client"
25
+	"github.com/docker/notary/passphrase"
26
+	"github.com/docker/notary/storage"
27
+	"github.com/docker/notary/trustmanager"
28
+	"github.com/docker/notary/trustpinning"
29
+	"github.com/docker/notary/tuf/data"
30
+	"github.com/docker/notary/tuf/signed"
31
+)
32
+
33
+var (
34
+	// ReleasesRole is the role named "releases"
35
+	ReleasesRole = path.Join(data.CanonicalTargetsRole, "releases")
36
+)
37
+
38
+func trustDirectory() string {
39
+	return filepath.Join(cliconfig.ConfigDir(), "trust")
40
+}
41
+
42
+// certificateDirectory returns the directory containing
43
+// TLS certificates for the given server. An error is
44
+// returned if there was an error parsing the server string.
45
+func certificateDirectory(server string) (string, error) {
46
+	u, err := url.Parse(server)
47
+	if err != nil {
48
+		return "", err
49
+	}
50
+
51
+	return filepath.Join(cliconfig.ConfigDir(), "tls", u.Host), nil
52
+}
53
+
54
+// Server returns the base URL for the trust server.
55
+func Server(index *registrytypes.IndexInfo) (string, error) {
56
+	if s := os.Getenv("DOCKER_CONTENT_TRUST_SERVER"); s != "" {
57
+		urlObj, err := url.Parse(s)
58
+		if err != nil || urlObj.Scheme != "https" {
59
+			return "", fmt.Errorf("valid https URL required for trust server, got %s", s)
60
+		}
61
+
62
+		return s, nil
63
+	}
64
+	if index.Official {
65
+		return registry.NotaryServer, nil
66
+	}
67
+	return "https://" + index.Name, nil
68
+}
69
+
70
+type simpleCredentialStore struct {
71
+	auth types.AuthConfig
72
+}
73
+
74
+func (scs simpleCredentialStore) Basic(u *url.URL) (string, string) {
75
+	return scs.auth.Username, scs.auth.Password
76
+}
77
+
78
+func (scs simpleCredentialStore) RefreshToken(u *url.URL, service string) string {
79
+	return scs.auth.IdentityToken
80
+}
81
+
82
+func (scs simpleCredentialStore) SetRefreshToken(*url.URL, string, string) {
83
+}
84
+
85
+// GetNotaryRepository returns a NotaryRepository which stores all the
86
+// information needed to operate on a notary repository.
87
+// It creates an HTTP transport providing authentication support.
88
+func GetNotaryRepository(streams command.Streams, repoInfo *registry.RepositoryInfo, authConfig types.AuthConfig, actions ...string) (*client.NotaryRepository, error) {
89
+	server, err := Server(repoInfo.Index)
90
+	if err != nil {
91
+		return nil, err
92
+	}
93
+
94
+	var cfg = tlsconfig.ClientDefault()
95
+	cfg.InsecureSkipVerify = !repoInfo.Index.Secure
96
+
97
+	// Get certificate base directory
98
+	certDir, err := certificateDirectory(server)
99
+	if err != nil {
100
+		return nil, err
101
+	}
102
+	logrus.Debugf("reading certificate directory: %s", certDir)
103
+
104
+	if err := registry.ReadCertsDirectory(cfg, certDir); err != nil {
105
+		return nil, err
106
+	}
107
+
108
+	base := &http.Transport{
109
+		Proxy: http.ProxyFromEnvironment,
110
+		Dial: (&net.Dialer{
111
+			Timeout:   30 * time.Second,
112
+			KeepAlive: 30 * time.Second,
113
+			DualStack: true,
114
+		}).Dial,
115
+		TLSHandshakeTimeout: 10 * time.Second,
116
+		TLSClientConfig:     cfg,
117
+		DisableKeepAlives:   true,
118
+	}
119
+
120
+	// Skip configuration headers since request is not going to Docker daemon
121
+	modifiers := registry.DockerHeaders(command.UserAgent(), http.Header{})
122
+	authTransport := transport.NewTransport(base, modifiers...)
123
+	pingClient := &http.Client{
124
+		Transport: authTransport,
125
+		Timeout:   5 * time.Second,
126
+	}
127
+	endpointStr := server + "/v2/"
128
+	req, err := http.NewRequest("GET", endpointStr, nil)
129
+	if err != nil {
130
+		return nil, err
131
+	}
132
+
133
+	challengeManager := challenge.NewSimpleManager()
134
+
135
+	resp, err := pingClient.Do(req)
136
+	if err != nil {
137
+		// Ignore error on ping to operate in offline mode
138
+		logrus.Debugf("Error pinging notary server %q: %s", endpointStr, err)
139
+	} else {
140
+		defer resp.Body.Close()
141
+
142
+		// Add response to the challenge manager to parse out
143
+		// authentication header and register authentication method
144
+		if err := challengeManager.AddResponse(resp); err != nil {
145
+			return nil, err
146
+		}
147
+	}
148
+
149
+	creds := simpleCredentialStore{auth: authConfig}
150
+	tokenHandler := auth.NewTokenHandler(authTransport, creds, repoInfo.FullName(), actions...)
151
+	basicHandler := auth.NewBasicHandler(creds)
152
+	modifiers = append(modifiers, transport.RequestModifier(auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)))
153
+	tr := transport.NewTransport(base, modifiers...)
154
+
155
+	return client.NewNotaryRepository(
156
+		trustDirectory(),
157
+		repoInfo.FullName(),
158
+		server,
159
+		tr,
160
+		getPassphraseRetriever(streams),
161
+		trustpinning.TrustPinConfig{})
162
+}
163
+
164
+func getPassphraseRetriever(streams command.Streams) notary.PassRetriever {
165
+	aliasMap := map[string]string{
166
+		"root":     "root",
167
+		"snapshot": "repository",
168
+		"targets":  "repository",
169
+		"default":  "repository",
170
+	}
171
+	baseRetriever := passphrase.PromptRetrieverWithInOut(streams.In(), streams.Out(), aliasMap)
172
+	env := map[string]string{
173
+		"root":     os.Getenv("DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE"),
174
+		"snapshot": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
175
+		"targets":  os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
176
+		"default":  os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
177
+	}
178
+
179
+	return func(keyName string, alias string, createNew bool, numAttempts int) (string, bool, error) {
180
+		if v := env[alias]; v != "" {
181
+			return v, numAttempts > 1, nil
182
+		}
183
+		// For non-root roles, we can also try the "default" alias if it is specified
184
+		if v := env["default"]; v != "" && alias != data.CanonicalRootRole {
185
+			return v, numAttempts > 1, nil
186
+		}
187
+		return baseRetriever(keyName, alias, createNew, numAttempts)
188
+	}
189
+}
190
+
191
+// NotaryError formats an error message received from the notary service
192
+func NotaryError(repoName string, err error) error {
193
+	switch err.(type) {
194
+	case *json.SyntaxError:
195
+		logrus.Debugf("Notary syntax error: %s", err)
196
+		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)
197
+	case signed.ErrExpired:
198
+		return fmt.Errorf("Error: remote repository %s out-of-date: %v", repoName, err)
199
+	case trustmanager.ErrKeyNotFound:
200
+		return fmt.Errorf("Error: signing keys for remote repository %s not found: %v", repoName, err)
201
+	case storage.NetworkError:
202
+		return fmt.Errorf("Error: error contacting notary server: %v", err)
203
+	case storage.ErrMetaNotFound:
204
+		return fmt.Errorf("Error: trust data missing for remote repository %s or remote repository not found: %v", repoName, err)
205
+	case trustpinning.ErrRootRotationFail, trustpinning.ErrValidationFail, signed.ErrInvalidKeyType:
206
+		return fmt.Errorf("Warning: potential malicious behavior - trust data mismatch for remote repository %s: %v", repoName, err)
207
+	case signed.ErrNoKeys:
208
+		return fmt.Errorf("Error: could not find signing keys for remote repository %s, or could not decrypt signing key: %v", repoName, err)
209
+	case signed.ErrLowVersion:
210
+		return fmt.Errorf("Warning: potential malicious behavior - trust data version is lower than expected for remote repository %s: %v", repoName, err)
211
+	case signed.ErrRoleThreshold:
212
+		return fmt.Errorf("Warning: potential malicious behavior - trust data has insufficient signatures for remote repository %s: %v", repoName, err)
213
+	case client.ErrRepositoryNotExist:
214
+		return fmt.Errorf("Error: remote trust data does not exist for %s: %v", repoName, err)
215
+	case signed.ErrInsufficientSignatures:
216
+		return fmt.Errorf("Error: could not produce valid signature for %s.  If Yubikey was used, was touch input provided?: %v", repoName, err)
217
+	}
218
+
219
+	return err
220
+}