Browse code

Move image trust related cli methods into the image package.

Signed-off-by: Daniel Nephin <dnephin@docker.com>

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