1package image
2
3import (
4	"fmt"
5	"os"
6	"path/filepath"
7	"strings"
8	"testing"
9
10	"github.com/docker/cli/e2e/internal/fixtures"
11	"github.com/docker/cli/internal/test/environment"
12	"github.com/docker/cli/internal/test/output"
13	"gotest.tools/assert"
14	"gotest.tools/fs"
15	"gotest.tools/golden"
16	"gotest.tools/icmd"
17	"gotest.tools/skip"
18)
19
20const (
21	notary = "/usr/local/bin/notary"
22
23	pubkey1  = "./testdata/notary/delgkey1.crt"
24	privkey1 = "./testdata/notary/delgkey1.key"
25	pubkey2  = "./testdata/notary/delgkey2.crt"
26	privkey2 = "./testdata/notary/delgkey2.key"
27	pubkey3  = "./testdata/notary/delgkey3.crt"
28	privkey3 = "./testdata/notary/delgkey3.key"
29	pubkey4  = "./testdata/notary/delgkey4.crt"
30	privkey4 = "./testdata/notary/delgkey4.key"
31)
32
33func TestPushWithContentTrust(t *testing.T) {
34	skip.If(t, environment.RemoteDaemon())
35
36	dir := fixtures.SetupConfigFile(t)
37	defer dir.Remove()
38	image := createImage(t, registryPrefix, "trust-push", "latest")
39
40	result := icmd.RunCmd(icmd.Command("docker", "push", image),
41		fixtures.WithConfig(dir.Path()),
42		fixtures.WithTrust,
43		fixtures.WithNotary,
44		fixtures.WithPassphrase("foo", "bar"),
45	)
46	result.Assert(t, icmd.Success)
47	golden.Assert(t, result.Stderr(), "push-with-content-trust-err.golden")
48	output.Assert(t, result.Stdout(), map[int]func(string) error{
49		0: output.Equals("The push refers to repository [registry:5000/trust-push]"),
50		1: output.Equals("5bef08742407: Preparing"),
51		3: output.Equals("latest: digest: sha256:641b95ddb2ea9dc2af1a0113b6b348ebc20872ba615204fbe12148e98fd6f23d size: 528"),
52		4: output.Equals("Signing and pushing trust metadata"),
53		5: output.Equals(`Finished initializing "registry:5000/trust-push"`),
54		6: output.Equals("Successfully signed registry:5000/trust-push:latest"),
55	})
56}
57
58func TestPushWithContentTrustUnreachableServer(t *testing.T) {
59	skip.If(t, environment.RemoteDaemon())
60
61	dir := fixtures.SetupConfigFile(t)
62	defer dir.Remove()
63	image := createImage(t, registryPrefix, "trust-push-unreachable", "latest")
64
65	result := icmd.RunCmd(icmd.Command("docker", "push", image),
66		fixtures.WithConfig(dir.Path()),
67		fixtures.WithTrust,
68		fixtures.WithNotaryServer("https://invalidnotaryserver"),
69	)
70	result.Assert(t, icmd.Expected{
71		ExitCode: 1,
72		Err:      "error contacting notary server",
73	})
74}
75
76func TestPushWithContentTrustExistingTag(t *testing.T) {
77	skip.If(t, environment.RemoteDaemon())
78
79	dir := fixtures.SetupConfigFile(t)
80	defer dir.Remove()
81	image := createImage(t, registryPrefix, "trust-push-existing", "latest")
82
83	result := icmd.RunCmd(icmd.Command("docker", "push", image))
84	result.Assert(t, icmd.Success)
85
86	result = icmd.RunCmd(icmd.Command("docker", "push", image),
87		fixtures.WithConfig(dir.Path()),
88		fixtures.WithTrust,
89		fixtures.WithNotary,
90		fixtures.WithPassphrase("foo", "bar"),
91	)
92	result.Assert(t, icmd.Expected{
93		Out: "Signing and pushing trust metadata",
94	})
95
96	// Re-push
97	result = icmd.RunCmd(icmd.Command("docker", "push", image),
98		fixtures.WithConfig(dir.Path()),
99		fixtures.WithTrust,
100		fixtures.WithNotary,
101		fixtures.WithPassphrase("foo", "bar"),
102	)
103	result.Assert(t, icmd.Expected{
104		Out: "Signing and pushing trust metadata",
105	})
106}
107
108func TestPushWithContentTrustReleasesDelegationOnly(t *testing.T) {
109	skip.If(t, environment.RemoteDaemon())
110
111	role := "targets/releases"
112
113	dir := fixtures.SetupConfigFile(t)
114	defer dir.Remove()
115	copyPrivateKey(t, dir.Join("trust", "private"), privkey1)
116	notaryDir := setupNotaryConfig(t, dir)
117	defer notaryDir.Remove()
118	homeDir := fs.NewDir(t, "push_test_home")
119	defer notaryDir.Remove()
120
121	baseRef := fmt.Sprintf("%s/%s", registryPrefix, "trust-push-releases-delegation")
122	targetRef := fmt.Sprintf("%s:%s", baseRef, "latest")
123
124	// Init repository
125	notaryInit(t, notaryDir, homeDir, baseRef)
126	// Add delegation key (public key)
127	notaryAddDelegation(t, notaryDir, homeDir, baseRef, role, pubkey1)
128	// Publish it
129	notaryPublish(t, notaryDir, homeDir, baseRef)
130	// Import private key
131	notaryImportPrivateKey(t, notaryDir, homeDir, baseRef, role, privkey1)
132
133	// Tag & push with content trust
134	icmd.RunCommand("docker", "pull", fixtures.AlpineImage).Assert(t, icmd.Success)
135	icmd.RunCommand("docker", "tag", fixtures.AlpineImage, targetRef).Assert(t, icmd.Success)
136	result := icmd.RunCmd(icmd.Command("docker", "push", targetRef),
137		fixtures.WithConfig(dir.Path()),
138		fixtures.WithTrust,
139		fixtures.WithNotary,
140		fixtures.WithPassphrase("foo", "foo"),
141	)
142	result.Assert(t, icmd.Expected{
143		Out: "Signing and pushing trust metadata",
144	})
145
146	targetsInRole := notaryListTargetsInRole(t, notaryDir, homeDir, baseRef, role)
147	assert.Assert(t, targetsInRole["latest"] == role, "%v", targetsInRole)
148	targetsInRole = notaryListTargetsInRole(t, notaryDir, homeDir, baseRef, "targets")
149	assert.Assert(t, targetsInRole["latest"] != "targets", "%v", targetsInRole)
150
151	result = icmd.RunCmd(icmd.Command("docker", "pull", targetRef),
152		fixtures.WithConfig(dir.Path()),
153		fixtures.WithTrust,
154		fixtures.WithNotary,
155	)
156	result.Assert(t, icmd.Success)
157}
158
159func TestPushWithContentTrustSignsAllFirstLevelRolesWeHaveKeysFor(t *testing.T) {
160	skip.If(t, environment.RemoteDaemon())
161
162	dir := fixtures.SetupConfigFile(t)
163	defer dir.Remove()
164	copyPrivateKey(t, dir.Join("trust", "private"), privkey1)
165	copyPrivateKey(t, dir.Join("trust", "private"), privkey2)
166	copyPrivateKey(t, dir.Join("trust", "private"), privkey3)
167	notaryDir := setupNotaryConfig(t, dir)
168	defer notaryDir.Remove()
169	homeDir := fs.NewDir(t, "push_test_home")
170	defer notaryDir.Remove()
171
172	baseRef := fmt.Sprintf("%s/%s", registryPrefix, "trust-push-releases-first-roles")
173	targetRef := fmt.Sprintf("%s:%s", baseRef, "latest")
174
175	// Init repository
176	notaryInit(t, notaryDir, homeDir, baseRef)
177	// Add delegation key (public key)
178	notaryAddDelegation(t, notaryDir, homeDir, baseRef, "targets/role1", pubkey1)
179	notaryAddDelegation(t, notaryDir, homeDir, baseRef, "targets/role2", pubkey2)
180	notaryAddDelegation(t, notaryDir, homeDir, baseRef, "targets/role3", pubkey3)
181	notaryAddDelegation(t, notaryDir, homeDir, baseRef, "targets/role1/subrole", pubkey3)
182	// Import private key
183	notaryImportPrivateKey(t, notaryDir, homeDir, baseRef, "targets/role1", privkey1)
184	notaryImportPrivateKey(t, notaryDir, homeDir, baseRef, "targets/role2", privkey2)
185	notaryImportPrivateKey(t, notaryDir, homeDir, baseRef, "targets/role1/subrole", privkey3)
186	// Publish it
187	notaryPublish(t, notaryDir, homeDir, baseRef)
188
189	// Tag & push with content trust
190	icmd.RunCommand("docker", "pull", fixtures.AlpineImage).Assert(t, icmd.Success)
191	icmd.RunCommand("docker", "tag", fixtures.AlpineImage, targetRef).Assert(t, icmd.Success)
192	result := icmd.RunCmd(icmd.Command("docker", "push", targetRef),
193		fixtures.WithConfig(dir.Path()),
194		fixtures.WithTrust,
195		fixtures.WithNotary,
196		fixtures.WithPassphrase("foo", "foo"),
197	)
198	result.Assert(t, icmd.Expected{
199		Out: "Signing and pushing trust metadata",
200	})
201
202	// check to make sure that the target has been added to targets/role1 and targets/role2, and
203	// not targets (because there are delegations) or targets/role3 (due to missing key) or
204	// targets/role1/subrole (due to it being a second level delegation)
205	targetsInRole := notaryListTargetsInRole(t, notaryDir, homeDir, baseRef, "targets/role1")
206	assert.Assert(t, targetsInRole["latest"] == "targets/role1", "%v", targetsInRole)
207	targetsInRole = notaryListTargetsInRole(t, notaryDir, homeDir, baseRef, "targets/role2")
208	assert.Assert(t, targetsInRole["latest"] == "targets/role2", "%v", targetsInRole)
209	targetsInRole = notaryListTargetsInRole(t, notaryDir, homeDir, baseRef, "targets")
210	assert.Assert(t, targetsInRole["latest"] != "targets", "%v", targetsInRole)
211
212	assert.NilError(t, os.RemoveAll(filepath.Join(dir.Join("trust"))))
213	// Try to pull, should fail because non of these are the release role
214	// FIXME(vdemeester) should be unit test
215	result = icmd.RunCmd(icmd.Command("docker", "pull", targetRef),
216		fixtures.WithConfig(dir.Path()),
217		fixtures.WithTrust,
218		fixtures.WithNotary,
219	)
220	result.Assert(t, icmd.Expected{
221		ExitCode: 1,
222	})
223}
224
225func TestPushWithContentTrustSignsForRolesWithKeysAndValidPaths(t *testing.T) {
226	skip.If(t, environment.RemoteDaemon())
227
228	dir := fixtures.SetupConfigFile(t)
229	defer dir.Remove()
230	copyPrivateKey(t, dir.Join("trust", "private"), privkey1)
231	copyPrivateKey(t, dir.Join("trust", "private"), privkey2)
232	copyPrivateKey(t, dir.Join("trust", "private"), privkey3)
233	copyPrivateKey(t, dir.Join("trust", "private"), privkey4)
234	notaryDir := setupNotaryConfig(t, dir)
235	defer notaryDir.Remove()
236	homeDir := fs.NewDir(t, "push_test_home")
237	defer notaryDir.Remove()
238
239	baseRef := fmt.Sprintf("%s/%s", registryPrefix, "trust-push-releases-keys-valid-paths")
240	targetRef := fmt.Sprintf("%s:%s", baseRef, "latest")
241
242	// Init repository
243	notaryInit(t, notaryDir, homeDir, baseRef)
244	// Add delegation key (public key)
245	notaryAddDelegation(t, notaryDir, homeDir, baseRef, "targets/role1", pubkey1, "l", "z")
246	notaryAddDelegation(t, notaryDir, homeDir, baseRef, "targets/role2", pubkey2, "x", "y")
247	notaryAddDelegation(t, notaryDir, homeDir, baseRef, "targets/role3", pubkey3, "latest")
248	notaryAddDelegation(t, notaryDir, homeDir, baseRef, "targets/role4", pubkey4, "latest")
249	// Import private keys (except 3rd key)
250	notaryImportPrivateKey(t, notaryDir, homeDir, baseRef, "targets/role1", privkey1)
251	notaryImportPrivateKey(t, notaryDir, homeDir, baseRef, "targets/role2", privkey2)
252	notaryImportPrivateKey(t, notaryDir, homeDir, baseRef, "targets/role4", privkey4)
253	// Publish it
254	notaryPublish(t, notaryDir, homeDir, baseRef)
255
256	// Tag & push with content trust
257	icmd.RunCommand("docker", "pull", fixtures.AlpineImage).Assert(t, icmd.Success)
258	icmd.RunCommand("docker", "tag", fixtures.AlpineImage, targetRef).Assert(t, icmd.Success)
259	result := icmd.RunCmd(icmd.Command("docker", "push", targetRef),
260		fixtures.WithConfig(dir.Path()),
261		fixtures.WithTrust,
262		fixtures.WithNotary,
263		fixtures.WithPassphrase("foo", "foo"),
264	)
265	result.Assert(t, icmd.Expected{
266		Out: "Signing and pushing trust metadata",
267	})
268
269	// check to make sure that the target has been added to targets/role1 and targets/role4, and
270	// not targets (because there are delegations) or targets/role2 (due to path restrictions) or
271	// targets/role3 (due to missing key)
272	targetsInRole := notaryListTargetsInRole(t, notaryDir, homeDir, baseRef, "targets/role1")
273	assert.Assert(t, targetsInRole["latest"] == "targets/role1", "%v", targetsInRole)
274	targetsInRole = notaryListTargetsInRole(t, notaryDir, homeDir, baseRef, "targets/role4")
275	assert.Assert(t, targetsInRole["latest"] == "targets/role4", "%v", targetsInRole)
276	targetsInRole = notaryListTargetsInRole(t, notaryDir, homeDir, baseRef, "targets")
277	assert.Assert(t, targetsInRole["latest"] != "targets", "%v", targetsInRole)
278
279	assert.NilError(t, os.RemoveAll(filepath.Join(dir.Join("trust"))))
280	// Try to pull, should fail because non of these are the release role
281	// FIXME(vdemeester) should be unit test
282	result = icmd.RunCmd(icmd.Command("docker", "pull", targetRef),
283		fixtures.WithConfig(dir.Path()),
284		fixtures.WithTrust,
285		fixtures.WithNotary,
286	)
287	result.Assert(t, icmd.Expected{
288		ExitCode: 1,
289	})
290}
291
292func createImage(t *testing.T, registryPrefix, repo, tag string) string {
293	image := fmt.Sprintf("%s/%s:%s", registryPrefix, repo, tag)
294	icmd.RunCommand("docker", "pull", fixtures.AlpineImage).Assert(t, icmd.Success)
295	icmd.RunCommand("docker", "tag", fixtures.AlpineImage, image).Assert(t, icmd.Success)
296	return image
297}
298
299func withNotaryPassphrase(pwd string) func(*icmd.Cmd) {
300	return func(c *icmd.Cmd) {
301		c.Env = append(c.Env, []string{
302			fmt.Sprintf("NOTARY_ROOT_PASSPHRASE=%s", pwd),
303			fmt.Sprintf("NOTARY_TARGETS_PASSPHRASE=%s", pwd),
304			fmt.Sprintf("NOTARY_SNAPSHOT_PASSPHRASE=%s", pwd),
305			fmt.Sprintf("NOTARY_DELEGATION_PASSPHRASE=%s", pwd),
306		}...)
307	}
308}
309
310func notaryImportPrivateKey(t *testing.T, notaryDir, homeDir *fs.Dir, baseRef, role, privkey string) {
311	icmd.RunCmd(
312		icmd.Command(notary, "-c", notaryDir.Join("client-config.json"), "key", "import", privkey, "-g", baseRef, "-r", role),
313		withNotaryPassphrase("foo"),
314		fixtures.WithHome(homeDir.Path()),
315	).Assert(t, icmd.Success)
316}
317
318func notaryPublish(t *testing.T, notaryDir, homeDir *fs.Dir, baseRef string) {
319	icmd.RunCmd(
320		icmd.Command(notary, "-c", notaryDir.Join("client-config.json"), "publish", baseRef),
321		withNotaryPassphrase("foo"),
322		fixtures.WithHome(homeDir.Path()),
323	).Assert(t, icmd.Success)
324}
325
326func notaryAddDelegation(t *testing.T, notaryDir, homeDir *fs.Dir, baseRef, role, pubkey string, paths ...string) {
327	pathsArg := "--all-paths"
328	if len(paths) > 0 {
329		pathsArg = "--paths=" + strings.Join(paths, ",")
330	}
331	icmd.RunCmd(
332		icmd.Command(notary, "-c", notaryDir.Join("client-config.json"), "delegation", "add", baseRef, role, pubkey, pathsArg),
333		withNotaryPassphrase("foo"),
334		fixtures.WithHome(homeDir.Path()),
335	).Assert(t, icmd.Success)
336}
337
338func notaryInit(t *testing.T, notaryDir, homeDir *fs.Dir, baseRef string) {
339	icmd.RunCmd(
340		icmd.Command(notary, "-c", notaryDir.Join("client-config.json"), "init", baseRef),
341		withNotaryPassphrase("foo"),
342		fixtures.WithHome(homeDir.Path()),
343	).Assert(t, icmd.Success)
344}
345
346func notaryListTargetsInRole(t *testing.T, notaryDir, homeDir *fs.Dir, baseRef, role string) map[string]string {
347	result := icmd.RunCmd(
348		icmd.Command(notary, "-c", notaryDir.Join("client-config.json"), "list", baseRef, "-r", role),
349		fixtures.WithHome(homeDir.Path()),
350	)
351	out := result.Combined()
352
353	// should look something like:
354	//    NAME                                 DIGEST                                SIZE (BYTES)    ROLE
355	// ------------------------------------------------------------------------------------------------------
356	//   latest   24a36bbc059b1345b7e8be0df20f1b23caa3602e85d42fff7ecd9d0bd255de56   1377           targets
357
358	targets := make(map[string]string)
359
360	// no target
361	lines := strings.Split(strings.TrimSpace(out), "\n")
362	if len(lines) == 1 && strings.Contains(out, "No targets present in this repository.") {
363		return targets
364	}
365
366	// otherwise, there is at least one target
367	assert.Assert(t, len(lines) >= 3, "output is %s", out)
368
369	for _, line := range lines[2:] {
370		tokens := strings.Fields(line)
371		assert.Assert(t, len(tokens) == 4)
372		targets[tokens[0]] = tokens[3]
373	}
374
375	return targets
376}
377
378func setupNotaryConfig(t *testing.T, dockerConfigDir fs.Dir) *fs.Dir {
379	return fs.NewDir(t, "notary_test", fs.WithMode(0700),
380		fs.WithFile("client-config.json", fmt.Sprintf(`
381{
382	"trust_dir": "%s",
383	"remote_server": {
384		"url": "%s"
385	}
386}`, dockerConfigDir.Join("trust"), fixtures.NotaryURL)),
387	)
388}
389
390func copyPrivateKey(t *testing.T, dir, source string) {
391	icmd.RunCommand("/bin/cp", source, dir).Assert(t, icmd.Success)
392}
393