github.com/khulnasoft/cli@v0.0.0-20240402070845-01bcad7beefa/e2e/image/push_test.go (about)

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