Browse Source

Trusted push now adds the tag to every first level delegation role for which we have keys
and for which the tag conforms to path restrictions.

Signed-off-by: cyli <cyli@twistedmatrix.com>

cyli 9 years ago
parent
commit
497a58e6e4

+ 71 - 22
api/client/trust.go

@@ -442,39 +442,88 @@ func (cli *DockerCli) trustedPush(repoInfo *registry.RepositoryInfo, tag string,
 		return err
 	}
 
-	if err := repo.AddTarget(target, releasesRole); err != nil {
-		return err
+	// get the latest repository metadata so we can figure out which roles to sign
+	_, err = repo.Update(false)
+
+	switch err.(type) {
+	case client.ErrRepoNotInitialized, client.ErrRepositoryNotExist:
+		keys := repo.CryptoService.ListKeys(data.CanonicalRootRole)
+		var rootKeyID string
+		// always select the first root key
+		if len(keys) > 0 {
+			sort.Strings(keys)
+			rootKeyID = keys[0]
+		} else {
+			rootPublicKey, err := repo.CryptoService.Create(data.CanonicalRootRole, data.ECDSAKey)
+			if err != nil {
+				return err
+			}
+			rootKeyID = rootPublicKey.ID()
+		}
+
+		// Initialize the notary repository with a remotely managed snapshot key
+		if err := repo.Initialize(rootKeyID, data.CanonicalSnapshotRole); err != nil {
+			return notaryError(repoInfo.FullName(), err)
+		}
+		fmt.Fprintf(cli.out, "Finished initializing %q\n", repoInfo.FullName())
+		err = repo.AddTarget(target, data.CanonicalTargetsRole)
+	case nil:
+		// already initialized and we have successfully downloaded the latest metadata
+		err = cli.addTargetToAllSignableRoles(repo, target)
+	default:
+		return notaryError(repoInfo.FullName(), err)
 	}
 
-	err = repo.Publish()
 	if err == nil {
-		fmt.Fprintf(cli.out, "Successfully signed %q:%s\n", repoInfo.FullName(), tag)
-		return nil
-	} else if _, ok := err.(client.ErrRepoNotInitialized); !ok {
+		err = repo.Publish()
+	}
+
+	if err != nil {
 		fmt.Fprintf(cli.out, "Failed to sign %q:%s - %s\n", repoInfo.FullName(), tag, err.Error())
 		return notaryError(repoInfo.FullName(), err)
 	}
 
-	keys := repo.CryptoService.ListKeys(data.CanonicalRootRole)
+	fmt.Fprintf(cli.out, "Successfully signed %q:%s\n", repoInfo.FullName(), tag)
+	return nil
+}
 
-	var rootKeyID string
-	// always select the first root key
-	if len(keys) > 0 {
-		sort.Strings(keys)
-		rootKeyID = keys[0]
-	} else {
-		rootPublicKey, err := repo.CryptoService.Create(data.CanonicalRootRole, data.ECDSAKey)
-		if err != nil {
-			return err
+// Attempt to add the image target to all the top level delegation roles we can
+// (based on whether we have the signing key and whether the role's path allows
+// us to).
+// If there are no delegation roles, we add to the targets role.
+func (cli *DockerCli) addTargetToAllSignableRoles(repo *client.NotaryRepository, target *client.Target) error {
+	var signableRoles []string
+
+	// translate the full key names, which includes the GUN, into just the key IDs
+	allCanonicalKeyIDs := make(map[string]string)
+	for fullKeyID := range repo.CryptoService.ListAllKeys() {
+		allCanonicalKeyIDs[path.Base(fullKeyID)] = ""
+	}
+
+	allDelegationRoles, err := repo.GetDelegationRoles()
+	if err != nil {
+		return err
+	}
+
+	for _, delegationRole := range allDelegationRoles {
+		// We do not support signing any delegation role that isn't a direct child of the targets role.
+		// Also don't bother checking the keys if we can't add the target
+		// to this role due to path restrictions
+		if path.Dir(delegationRole.Name) != data.CanonicalTargetsRole || !delegationRole.CheckPaths(target.Name) {
+			fmt.Println("skipping", delegationRole.Name)
+			continue
+		}
+
+		for _, canonicalKeyID := range delegationRole.KeyIDs {
+			if _, ok := allCanonicalKeyIDs[canonicalKeyID]; ok {
+				signableRoles = append(signableRoles, delegationRole.Name)
+			}
 		}
-		rootKeyID = rootPublicKey.ID()
 	}
 
-	// Initialize the notary repository with a remotely managed snapshot key
-	if err := repo.Initialize(rootKeyID, data.CanonicalSnapshotRole); err != nil {
-		return notaryError(repoInfo.FullName(), err)
+	if len(allDelegationRoles) > 0 && len(signableRoles) == 0 {
+		return fmt.Errorf("no valid signing keys for delegation roles")
 	}
-	fmt.Fprintf(cli.out, "Finished initializing %q\n", repoInfo.FullName())
 
-	return notaryError(repoInfo.FullName(), repo.Publish())
+	return repo.AddTarget(target, signableRoles...)
 }

+ 116 - 10
integration-cli/docker_cli_push_test.go

@@ -497,23 +497,29 @@ func (s *DockerTrustSuite) TestTrustedPushWithExpiredTimestamp(c *check.C) {
 	})
 }
 
-func (s *DockerTrustSuite) TestTrustedPushWithReleasesDelegation(c *check.C) {
+func (s *DockerTrustSuite) TestTrustedPushWithReleasesDelegationOnly(c *check.C) {
 	testRequires(c, NotaryHosting)
-	repoName := fmt.Sprintf("%v/dockerclireleasedelegation/trusted", privateRegistryURL)
+	repoName := fmt.Sprintf("%v/dockerclireleasedelegationinitfirst/trusted", privateRegistryURL)
 	targetName := fmt.Sprintf("%s:latest", repoName)
 	pwd := "12345678"
-	s.setupDelegations(c, repoName, pwd)
+	s.notaryInitRepo(c, repoName, pwd)
+	s.notaryCreateDelegation(c, repoName, pwd, "targets/releases", s.not.keys[0].Public)
+	s.notaryPublish(c, repoName, pwd)
+
+	s.notaryImportKey(c, repoName, "targets/releases", s.not.keys[0].Private)
 
 	// tag the image and upload it to the private registry
 	dockerCmd(c, "tag", "busybox", targetName)
 
-	pushCmd := exec.Command(dockerBinary, "-D", "push", targetName)
+	pushCmd := exec.Command(dockerBinary, "push", targetName)
 	s.trustedCmdWithPassphrases(pushCmd, pwd, pwd)
 	out, _, err := runCommandWithOutput(pushCmd)
 	c.Assert(err, check.IsNil, check.Commentf("trusted push failed: %s\n%s", err, out))
 	c.Assert(out, checker.Contains, "Signing and pushing trust metadata", check.Commentf("Missing expected output on trusted push with existing tag"))
 
 	// Try pull after push
+	os.RemoveAll(filepath.Join(cliconfig.ConfigDir(), "trust"))
+
 	pullCmd := exec.Command(dockerBinary, "pull", targetName)
 	s.trustedCmd(pullCmd)
 	out, _, err = runCommandWithOutput(pullCmd)
@@ -521,13 +527,113 @@ func (s *DockerTrustSuite) TestTrustedPushWithReleasesDelegation(c *check.C) {
 	c.Assert(string(out), checker.Contains, "Status: Downloaded", check.Commentf(out))
 
 	// check to make sure that the target has been added to targets/releases and not targets
-	contents, err := ioutil.ReadFile(filepath.Join(cliconfig.ConfigDir(), "trust/tuf", repoName, "metadata/targets.json"))
-	c.Assert(err, check.IsNil, check.Commentf("Unable to read targets metadata"))
-	c.Assert(strings.Contains(string(contents), `"latest"`), checker.False, check.Commentf(string(contents)))
+	s.assertTargetInDelegationRoles(c, repoName, "latest", "targets/releases")
+}
+
+func (s *DockerTrustSuite) TestTrustedPushSignsAllFirstLevelRolesWeHaveKeysFor(c *check.C) {
+	testRequires(c, NotaryHosting)
+	repoName := fmt.Sprintf("%v/dockerclimanyroles/trusted", privateRegistryURL)
+	targetName := fmt.Sprintf("%s:latest", repoName)
+	pwd := "12345678"
+	s.notaryInitRepo(c, repoName, pwd)
+	s.notaryCreateDelegation(c, repoName, pwd, "targets/role1", s.not.keys[0].Public)
+	s.notaryCreateDelegation(c, repoName, pwd, "targets/role2", s.not.keys[1].Public)
+	s.notaryCreateDelegation(c, repoName, pwd, "targets/role3", s.not.keys[2].Public)
+
+	// import everything except the third key
+	s.notaryImportKey(c, repoName, "targets/role1", s.not.keys[0].Private)
+	s.notaryImportKey(c, repoName, "targets/role2", s.not.keys[1].Private)
+
+	s.notaryCreateDelegation(c, repoName, pwd, "targets/role1/subrole", s.not.keys[3].Public)
+	s.notaryImportKey(c, repoName, "targets/role1/subrole", s.not.keys[3].Private)
+
+	s.notaryPublish(c, repoName, pwd)
+
+	// tag the image and upload it to the private registry
+	dockerCmd(c, "tag", "busybox", targetName)
+
+	pushCmd := exec.Command(dockerBinary, "push", targetName)
+	s.trustedCmdWithPassphrases(pushCmd, pwd, pwd)
+	out, _, err := runCommandWithOutput(pushCmd)
+	c.Assert(err, check.IsNil, check.Commentf("trusted push failed: %s\n%s", err, out))
+	c.Assert(out, checker.Contains, "Signing and pushing trust metadata", check.Commentf("Missing expected output on trusted push with existing tag"))
+
+	// Try pull after push
+	os.RemoveAll(filepath.Join(cliconfig.ConfigDir(), "trust"))
+
+	pullCmd := exec.Command(dockerBinary, "pull", targetName)
+	s.trustedCmd(pullCmd)
+	out, _, err = runCommandWithOutput(pullCmd)
+	c.Assert(err, check.IsNil, check.Commentf(out))
+	c.Assert(string(out), checker.Contains, "Status: Downloaded", check.Commentf(out))
+
+	// check to make sure that the target has been added to targets/role1 and targets/role2, and
+	// not targets (because there are delegations) or targets/role3 (due to missing key) or
+	// targets/role1/subrole (due to it being a second level delegation)
+	s.assertTargetInDelegationRoles(c, repoName, "latest", "targets/role1", "targets/role2")
+}
+
+func (s *DockerTrustSuite) TestTrustedPushSignsForRolesWithKeysAndValidPaths(c *check.C) {
+	repoName := fmt.Sprintf("%v/dockerclirolesbykeysandpaths/trusted", privateRegistryURL)
+	targetName := fmt.Sprintf("%s:latest", repoName)
+	pwd := "12345678"
+	s.notaryInitRepo(c, repoName, pwd)
+	s.notaryCreateDelegation(c, repoName, pwd, "targets/role1", s.not.keys[0].Public, "l", "z")
+	s.notaryCreateDelegation(c, repoName, pwd, "targets/role2", s.not.keys[1].Public, "x", "y")
+	s.notaryCreateDelegation(c, repoName, pwd, "targets/role3", s.not.keys[2].Public, "latest")
+	s.notaryCreateDelegation(c, repoName, pwd, "targets/role4", s.not.keys[3].Public, "latest")
 
-	contents, err = ioutil.ReadFile(filepath.Join(cliconfig.ConfigDir(), "trust/tuf", repoName, "metadata/targets/releases.json"))
-	c.Assert(err, check.IsNil, check.Commentf("Unable to read targets/releases metadata"))
-	c.Assert(string(contents), checker.Contains, `"latest"`, check.Commentf(string(contents)))
+	// import everything except the third key
+	s.notaryImportKey(c, repoName, "targets/role1", s.not.keys[0].Private)
+	s.notaryImportKey(c, repoName, "targets/role2", s.not.keys[1].Private)
+	s.notaryImportKey(c, repoName, "targets/role4", s.not.keys[3].Private)
+
+	s.notaryPublish(c, repoName, pwd)
+
+	// tag the image and upload it to the private registry
+	dockerCmd(c, "tag", "busybox", targetName)
+
+	pushCmd := exec.Command(dockerBinary, "push", targetName)
+	s.trustedCmdWithPassphrases(pushCmd, pwd, pwd)
+	out, _, err := runCommandWithOutput(pushCmd)
+	c.Assert(err, check.IsNil, check.Commentf("trusted push failed: %s\n%s", err, out))
+	c.Assert(out, checker.Contains, "Signing and pushing trust metadata", check.Commentf("Missing expected output on trusted push with existing tag"))
+
+	// Try pull after push
+	os.RemoveAll(filepath.Join(cliconfig.ConfigDir(), "trust"))
+
+	pullCmd := exec.Command(dockerBinary, "pull", targetName)
+	s.trustedCmd(pullCmd)
+	out, _, err = runCommandWithOutput(pullCmd)
+	c.Assert(err, check.IsNil, check.Commentf(out))
+	c.Assert(string(out), checker.Contains, "Status: Downloaded", check.Commentf(out))
+
+	// check to make sure that the target has been added to targets/role1 and targets/role4, and
+	// not targets (because there are delegations) or targets/role2 (due to path restrictions) or
+	// targets/role3 (due to missing key)
+	s.assertTargetInDelegationRoles(c, repoName, "latest", "targets/role1", "targets/role4")
+}
+
+func (s *DockerTrustSuite) TestTrustedPushDoesntSignTargetsIfDelegationsExist(c *check.C) {
+	testRequires(c, NotaryHosting)
+	repoName := fmt.Sprintf("%v/dockerclireleasedelegationnotsignable/trusted", privateRegistryURL)
+	targetName := fmt.Sprintf("%s:latest", repoName)
+	pwd := "12345678"
+	s.notaryInitRepo(c, repoName, pwd)
+	s.notaryCreateDelegation(c, repoName, pwd, "targets/role1", s.not.keys[0].Public)
+	s.notaryPublish(c, repoName, pwd)
+
+	// do not import any delegations key
+
+	// tag the image and upload it to the private registry
+	dockerCmd(c, "tag", "busybox", targetName)
+
+	pushCmd := exec.Command(dockerBinary, "push", targetName)
+	s.trustedCmdWithPassphrases(pushCmd, pwd, pwd)
+	out, _, err := runCommandWithOutput(pushCmd)
+	c.Assert(err, check.Not(check.IsNil), check.Commentf("trusted push succeed but should have failed:\n%s", out))
+	c.Assert(out, checker.Contains, "no valid signing keys",
+		check.Commentf("Missing expected output on trusted push without keys"))
 }
 
 func (s *DockerRegistryAuthHtpasswdSuite) TestPushNoCredentialsNoRetry(c *check.C) {

+ 24 - 0
integration-cli/fixtures/notary/delgkey1.crt

@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIID8jCCAtqgAwIBAgIJAJkxr+7rAgXbMA0GCSqGSIb3DQEBBQUAMFgxCzAJBgNV
+BAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEPMA0G
+A1UEChMGRG9ja2VyMRMwEQYDVQQDEwpkZWxlZ2F0aW9uMCAXDTE2MDMwODAyNDEy
+MFoYDzIxMTYwMjEzMDI0MTIwWjBYMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0Ex
+FjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xDzANBgNVBAoTBkRvY2tlcjETMBEGA1UE
+AxMKZGVsZWdhdGlvbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJuz
+To1qoL/RY5pNxdPkP/jiO3f/RTvz20C90EweaKgRdIV/vTUUE+mMRQulpf1vpCP9
+uidGfEoJcq4jM1H59XTYUoUvGbAMP3Iu7Uz0rF5v+Glm82Z0WGI+PkOnwRN2bJi4
+LhAch6QlA/48IOFH/O9jnHYMb45lQFpm+gOvatRyGkPZCftD3ntkhVMk1OJ7EZC4
+LYiwzmuPEYusO/qVgcHkGtIxLWAjGmDzrV3Q5orPVwwUOxNQdRRU1L2bhfUsodcb
+Fgi/LCz4xnGx4YpF0O24Y7/0SPotSyaT0RYyj/j/bIKvYB20g4P7469klde1Ariz
+UEIf12PlaJ/H/PaIlEcCAwEAAaOBvDCBuTAdBgNVHQ4EFgQUXZK4ZGswIq54W4VZ
+OJY7zXvvndwwgYkGA1UdIwSBgTB/gBRdkrhkazAirnhbhVk4ljvNe++d3KFcpFow
+WDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1TYW4gRnJhbmNp
+c2NvMQ8wDQYDVQQKEwZEb2NrZXIxEzARBgNVBAMTCmRlbGVnYXRpb26CCQCZMa/u
+6wIF2zAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQA2ktnjxB2lXF+g
+lTc2qp9LhjofgCWKwLgWEeCwXI2nUBNE4n00hA9or2wer2viWC4IJnG0kTyMzzYT
+m1lBpZ8BP6S3sSkvhohaqS+gBIUVB7U65tAof/SY2UHpeVJ1YpTE4F1GAUfqSY7V
+6IGHZAGiLeUS5kC6pzZA4siBhyCoYKRKEb9R82jSCHeFYS3ntwY1/gqcO/uIidVE
+2hLHlx6vBx9BEfXv31AGLoB3YocSTZLATwlrDHUQG1+oNh5ejQU1x/z+Y62EG5Jb
+u0yLDdJeSgup/DzPEoNpSihtdQZytKMK+KBmh22gDA5h+a6620zTZwCvJYxH9kkM
+IClUWwuD
+-----END CERTIFICATE-----

+ 27 - 0
integration-cli/fixtures/notary/delgkey1.key

@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAm7NOjWqgv9Fjmk3F0+Q/+OI7d/9FO/PbQL3QTB5oqBF0hX+9
+NRQT6YxFC6Wl/W+kI/26J0Z8SglyriMzUfn1dNhShS8ZsAw/ci7tTPSsXm/4aWbz
+ZnRYYj4+Q6fBE3ZsmLguEByHpCUD/jwg4Uf872OcdgxvjmVAWmb6A69q1HIaQ9kJ
++0Pee2SFUyTU4nsRkLgtiLDOa48Ri6w7+pWBweQa0jEtYCMaYPOtXdDmis9XDBQ7
+E1B1FFTUvZuF9Syh1xsWCL8sLPjGcbHhikXQ7bhjv/RI+i1LJpPRFjKP+P9sgq9g
+HbSDg/vjr2SV17UCuLNQQh/XY+Von8f89oiURwIDAQABAoIBAB7DhfDRMaPU5n41
+gbIFNlKhuKhUCsT2wMqA9qgjlgAnOsOp4qObLPgHXBkaCLsTlPX7iw15ktM6HKul
+jt1SqxoEKAHitYugT+Tqur5q1afvLcD9s3f54wC+VaUefzquOnTOZ2ONj4tyOODB
+1qlMhQBzyRVWDbCv9tAl6p5RyaTh+8IULctlER6w9m3upT9NxoRi1PrPBCRiEKKo
+4zDRvfbT/0ucLD20GS6trPv4ihTCTU7ydFujioDkFyNzCzYNGBnImpQ9/xeT5/Ys
+IJQy9Tdn6V0rXMBBb1EhyBQYw5Oxy6d6tzhjvva6LaJBGo9yzX0NHt58Ymhgm1q/
+vscj1pECgYEAyegQFP7dkmUdXdNpdrIdCvKlvni3r/hwB/9H0sJHIJbfTusfzeLL
+5Q8QSZAsaR7tSgJfr9GMdOjntvefYjKLfl3SnG/wF91m05eYfkeiZXc9RGe+XXGu
+wv5u2m/G7a05XpW1JFX+1ORyj2x5KsvF7KDtWJyR5ryIsOwHZNGQpJ8CgYEAxWoo
+r2eJBc9Xj5bhhS0VxUFODXImfeQF2aG2rSeuWMY7k4vmVkJwhBZiPW/dHBu1aMPh
+/SY1W7cgzdVIf2RIF5MgzzkmoisEApZTiSwmP6A2bTx6miXwFCLTCHIDfiXJ0tQA
+Nb+Ln+exks4BfCgKHOqWTcWizKNE/8Gb6SnhB1kCgYAgM1Z9QrhrpJyuXg0v1PA0
+0sYEPpRtCB416Ey4HCvj0qwClhUYbNc/zMs4MDok+b22U/KWw8C21H4/+/X7XzxI
+BwaT1HZiF/lSPZcgbKRFsmKfCjyeAodwqctcIv+C4GGJ6C5fgSeHJHfwz8fzP1Rt
+jKzNuQq71c2nCb2UIqgC2QKBgEieoJDFmVYVy7P6YMNIrnV9bGTt1NMCilRgdH6F
+1lC5uzivge/BSPqN8V2AROoOF1GOnRcucvpmBx8wkhaqoQprCOqxr1CAWl1JRzly
+kC9flCXi1YbW5dXCabb1metRo0h2zAz5hTcxV9UVCt7NK8svUFMTnKuCc+NRKTVA
+PpMhAoGBAJ9rFgZpWHRVuzsangbGslq3fDYGENLJ2fPNjLgfgVLi+YotG9jfgQPW
+QCvoSA1CChxzEJEB5hzEOEv9pThnBNg1LWNj+a3N5anW2UBHMEWeCrVFZwJMVdSd
+srUFtap7da8iUddc+sHC5hHHFDBdqG4pDck/uTs3CNWRF/ZqzE/G
+-----END RSA PRIVATE KEY-----

+ 24 - 0
integration-cli/fixtures/notary/delgkey2.crt

@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIID8jCCAtqgAwIBAgIJAMi/AxlwFquJMA0GCSqGSIb3DQEBBQUAMFgxCzAJBgNV
+BAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEPMA0G
+A1UEChMGRG9ja2VyMRMwEQYDVQQDEwpkZWxlZ2F0aW9uMCAXDTE2MDMwODAyNDEy
+MloYDzIxMTYwMjEzMDI0MTIyWjBYMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0Ex
+FjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xDzANBgNVBAoTBkRvY2tlcjETMBEGA1UE
+AxMKZGVsZWdhdGlvbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL/a
+1GO+02jt1p0sME+YGaK4+uZ9jezrpkCXKMsMfItgqCKRTX7YVuR7tnRt/Y1DNVqR
+nMeGc77soDag6eW4xrYrv9LwylUsOLanvK1d/8hDxZhzJjqlJBmz6BvLWDZUF9uu
+OjULL8yuP2cmRogjn0bqmdeKztrZtDQqQiwsG02nVjfuvVi3rP4G4DhL5fUoHB0R
+E6L9Su3/2OWGpdxZqkT7GAbjgLl4/4CXs00493m8xZIHXQ9559PiVlLfk6p6FjEV
+7irZp7XXSe1My/0HGebFXkYqEL9+My2od4w+qJmBT23aTduGTo8IZC7g9lwKEykA
+hWrYhR5tjkLvOsQIE7ECAwEAAaOBvDCBuTAdBgNVHQ4EFgQUHtEAVcwI3k7W5B6c
+L3w+eKQRsIYwgYkGA1UdIwSBgTB/gBQe0QBVzAjeTtbkHpwvfD54pBGwhqFcpFow
+WDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1TYW4gRnJhbmNp
+c2NvMQ8wDQYDVQQKEwZEb2NrZXIxEzARBgNVBAMTCmRlbGVnYXRpb26CCQDIvwMZ
+cBariTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAfjsMtZ+nJ7y5t
+rH9xPwWMLmtC5MwzDRvTUAGbNbFwwm8AncfvsDmmERqsr8L2qhY8CZ9vsN4NjjBn
+QRmM/ynYA8JTbf/5ZNDnD4D6qTXLgGFqyHcBaorcB9uQ8eiMOFAbhxLYfPrKaYdV
+qj+MejcFa3HmzmYCSqsvxRhSje5b4sORe9/3jNheXsX8VZUpWtCHc3k4GiCU6KyS
+gpnXkShU4sG92cK72L8pxmGTz8ynNMj/9WKkLxpNIv5u0/D01a3z4wx5k1zfRZiz
+IQS+xqxV/ztY844MDknxENlYzcqGj0Fd6hE5OKZxnGaH83A5adldMLlnhG1rscGP
+as9uwPYP
+-----END CERTIFICATE-----

+ 27 - 0
integration-cli/fixtures/notary/delgkey2.key

@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAv9rUY77TaO3WnSwwT5gZorj65n2N7OumQJcoywx8i2CoIpFN
+fthW5Hu2dG39jUM1WpGcx4ZzvuygNqDp5bjGtiu/0vDKVSw4tqe8rV3/yEPFmHMm
+OqUkGbPoG8tYNlQX2646NQsvzK4/ZyZGiCOfRuqZ14rO2tm0NCpCLCwbTadWN+69
+WLes/gbgOEvl9SgcHRETov1K7f/Y5Yal3FmqRPsYBuOAuXj/gJezTTj3ebzFkgdd
+D3nn0+JWUt+TqnoWMRXuKtmntddJ7UzL/QcZ5sVeRioQv34zLah3jD6omYFPbdpN
+24ZOjwhkLuD2XAoTKQCFatiFHm2OQu86xAgTsQIDAQABAoIBAQCDdASic1WXR58N
+AgH4B1dJT0VaOBzOgIfhKbEhruSG+ys4xCY9Cy4+TyWskNBxweMEs1CgxVb5Mlh0
+Fb0tUXWVzFQazDFWOn6BaFy2zPk81nLFCeDfvdcGZWZb5PAECYpvUuk+/vM5Ywq+
+OlOJZB72EDhonwssmI4IUAwXCAGNKjLfC4L+3ZgA3+I1xgxisJ2XWNYSLwHzIDRh
+U3zO2NpJi1edTNPltDBTb4iFhajX0SFgbARc+XVTpA3pgQujWo6CNB5YKCPuzIqr
+GFsvGSZDVzOUnfOlitaYNW+QIWAQf8VLWULwyFrS5Cb2WR/k7AmojZVuDHvzWrtg
+ZMG6b1mBAoGBAOV+3SiX8+khKBpxnOJLq0XlGjFNDWNNB34UIMVkFehRxpUr2261
+HDp4YiC9n7l47sjeFBk4IQf2vG/5MPpuqIixsk2W3siCASdMQypVZMG+zj6xDFfH
+8rwQSeZhwjmk2a+A7qgnhqvd/qa7EYOnsn1tLf2iBB2EaHV9lWBJFX0lAoGBANYD
+GbAPEiwh4Fns8pf59T3Lp0Q9XvAN3uh4DUU0mFrQ1HQHeXIw1IDCJ9JiRjLX7aHu
+79EtDssVPQ9dv0MN5rRULtrutCfRLsomm385PLLBIgBdVApnVvJJIWhQkFFMrhFt
+UP+483utiDOcCVXMxAy+1jx23EiWvl2H0xGIwsSdAoGBAMIcM+OJ4vxk1w7G2fNu
+HUfZJ/ZbPd+n35Z8X9uVdBI0WMsDdW6GMYIjIJygxuCRsSak8EsEdqvNvkTXeN3Z
+iyNTaYTG/1iI3YDnuEeuQrK9OKU+CzqUHHOFM3xxY15uWNFhNHt2MypbcnCD+aRp
+y0bbefL1fpWY0OHPfvEZ39shAoGAPbVdJc/irIkEGMni1YGEflIHo/ySMGO/f4aG
+RQs6Vw1aBS7WjN+ZlprlQpuFpElwwr2TttvoJRS1q4WbjakneZ3AeO5VUhnWBQIG
+2jNV1jEsLbC7d9h+UJRXpq18P4T9uBauQV5CDspluIPoiS3m5cntGjgnomKc93kf
+mjG1/10CgYA7kgOOva64sjWakL/IgDRiwr0YrJkAfPUZYwxYLHuiW9izUgngpqWd
+1wtq+YCsc4l7t8u9Tahb8OE0KSN5RC6QM6b8yW9qFDZ68QAX00+sN6di4qyAZlm+
+rK05W/3JmyvQbvO+JVRQtegZ1ExCj7LGuGOQ5KIpWsBEM3ic9ZP9gw==
+-----END RSA PRIVATE KEY-----

+ 24 - 0
integration-cli/fixtures/notary/delgkey3.crt

@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIID8jCCAtqgAwIBAgIJAI3uONxeFQJtMA0GCSqGSIb3DQEBBQUAMFgxCzAJBgNV
+BAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEPMA0G
+A1UEChMGRG9ja2VyMRMwEQYDVQQDEwpkZWxlZ2F0aW9uMCAXDTE2MDMwODAyNDEy
+NFoYDzIxMTYwMjEzMDI0MTI0WjBYMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0Ex
+FjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xDzANBgNVBAoTBkRvY2tlcjETMBEGA1UE
+AxMKZGVsZWdhdGlvbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOii
+Ij01MkSExgurs5owUNxNgRvrZFxNSNGfnscYZiaT/wNcocrOz40vvY29SOBEbCSW
+oBlCi0rYu/7LZBqvsP3YItmifpJHGfRiZ6xEQ4rKznY8+8E3FHVChlmVv9x6QPhA
+9OpATlSLvcdiXHbohdc+kQsl9qM93+QadRQLmtZ6H5Sv90d1MHNViX+8d/k2WyT0
+8u6fNv0ZHeltnZFYruF82YKJCOPdAJnCLUOXWRSG6xDhhvSewjxz6gFla5n8m+D9
+jvmIUUjoMEhjORUIVeA/lXT0AT3Lx0xE8uyhJQbp+hGtcPCcwYFZdz3yLcrxKO47
+nh6qOygf7I2fiR1ogqECAwEAAaOBvDCBuTAdBgNVHQ4EFgQUUqsFJdVoos2aewDh
+m1r66zyXeI4wgYkGA1UdIwSBgTB/gBRSqwUl1WiizZp7AOGbWvrrPJd4jqFcpFow
+WDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1TYW4gRnJhbmNp
+c2NvMQ8wDQYDVQQKEwZEb2NrZXIxEzARBgNVBAMTCmRlbGVnYXRpb26CCQCN7jjc
+XhUCbTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQADcyno8/AwNatf
+pjgILCZl1DVrqaKEVbp6ciNgVRCF4cM0bE5W4mjd5tO8d3/yTilry2FPicqxiMps
+oGroMFR+X1cZbOf0U32FyEW4EyWm2jjbiuEpnM5J/EeB/QfckqP6whS/QAM7PxDV
+Sxd8sKDb9SOGZiickFU4QpG1fdmY/knrrtbzRl7Nk/3tBgRaq+Brg7YNZZKlpUNB
+Hp3q0E+MFgVAojpcL7w1oSgoNev+cUNaBdPEmWIEi7F5rosCzmAIhuIY+ghmo9Qg
+zy+byAcxLpujl8vZvE1nZKMKZ7oJayOOgjB2Ztk6bO1r+GPtK5VfqEPhKTRDbBlo
+xS3tSCDJ
+-----END CERTIFICATE-----

+ 27 - 0
integration-cli/fixtures/notary/delgkey3.key

@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA6KIiPTUyRITGC6uzmjBQ3E2BG+tkXE1I0Z+exxhmJpP/A1yh
+ys7PjS+9jb1I4ERsJJagGUKLSti7/stkGq+w/dgi2aJ+kkcZ9GJnrERDisrOdjz7
+wTcUdUKGWZW/3HpA+ED06kBOVIu9x2JcduiF1z6RCyX2oz3f5Bp1FAua1noflK/3
+R3Uwc1WJf7x3+TZbJPTy7p82/Rkd6W2dkViu4XzZgokI490AmcItQ5dZFIbrEOGG
+9J7CPHPqAWVrmfyb4P2O+YhRSOgwSGM5FQhV4D+VdPQBPcvHTETy7KElBun6Ea1w
+8JzBgVl3PfItyvEo7jueHqo7KB/sjZ+JHWiCoQIDAQABAoIBADvh8HpdBTGKFAjR
+DAx2v3nWIZP0RgNUiZgcRJzvdOwdUJmm8KbqEZdAYMTpoqbINiY8971I2d5MaCgw
+ZvZPn3nYdzAamgZBczbrVdCMSe6iQf9Bt3SHHycIFtlcqOSyO6Mr5V+fagptZk66
+zR52wG0l1+RMw25F8SogfV7JlfP7Qh5Bob0lEN2xpbhwLiNaaB+IHNe0FelmRvmJ
+VUonoD0xaos25EXUES7J/9coiBqgRlDVHdUM0oaa/94UnxNPJnoNfte0yd+mC4LZ
+JVHo0Zti3x/8SiCYMbLQs5L8AL8VtPu9OPfur/J8+9Rv0Rh+L1Ben+JWzCzUw1Cj
+abH1zvkCgYEA9Q06Lu69ZLD31fTv46CphN+dGS/VgvMELkob6VQOhbV3RPhe6vqL
+p7D67J53iq4rZY5KX3zuXZ+A5s48atc8gz+hTsrE022QVXmO2ZrE22bEpL+mwpsB
+8//ul1UG51XTw6YR9CmLLD3Y4BgMjhSllx4Wwr9e9+PKl+DuSreqhxMCgYEA8wbf
+P3zh85jPN92gBG8+VIbVlXYOTK0OllYUoLt4URmLRllmrB6LyRlpODCyb9+JymMg
+WvAq5Bc0h8gMbSQEkYaAUq2CfSbyExASUHA+/nZglsTZhPkg5PJImntK6S58KAM7
+RJzyz20gxYA5H4KXFSiF+ONOE9X/cFUPxzF1AfsCgYBfgUY54GYEBkyxIIMWDhnD
+ZXtOw6vNG3V3rP5v04jNZ8oSIVKs9fTT6FADREeGzxauv+QQjxo/dtjAG4TEhxpY
+dMYjdTd8x2jHR1b7TCyI7eaZ5u/RTKRYOlj8tfC43GRqDiFVLZPGLFyIChdqkHVx
+DhME15zls+vTgaCdkjNt7QKBgQCfwDywNx8wSZqtVnoBcD7AwYFUpi3wKTIVkLAu
+mA0XAnuS2uGq8slgf9uynBAvifnBmDeEj6siFD7roozIkYyPPKLNtlC4hAlMjpv7
+VE2UZ6xGb0+tITaGSN2A7trnPS9P/g/PonvZ7hpEuWzTUbyOo/ytBn4ke99VsBSX
+E+OeUQKBgQCgmcwCj2/IH+GOpe9qAG6MTMKK7k22O8fBCrcDybL1pMWIesJEbzpv
+T5Atcx9L5ff6Q4Ysghb8ebXsErv4oZ72xyAwWJmbIaPllWn2ffUikzL3grSriWZy
+0bz6P9sRqYpbdmX3oVvTfBP5kbv+mtDXOB3h5rGfczKWNMyuZmxDOg==
+-----END RSA PRIVATE KEY-----

+ 24 - 0
integration-cli/fixtures/notary/delgkey4.crt

@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIID8jCCAtqgAwIBAgIJAKKDRMrryBRKMA0GCSqGSIb3DQEBBQUAMFgxCzAJBgNV
+BAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEPMA0G
+A1UEChMGRG9ja2VyMRMwEQYDVQQDEwpkZWxlZ2F0aW9uMCAXDTE2MDMwODAyNDEy
+N1oYDzIxMTYwMjEzMDI0MTI3WjBYMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0Ex
+FjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xDzANBgNVBAoTBkRvY2tlcjETMBEGA1UE
+AxMKZGVsZWdhdGlvbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOCf
+Wfff5mX/ko/Y790O04eR7h8/4YtZU3LFItcjhkphMf2V2BRlhWwwW6v96gTN1xsZ
+1il6/YXjviWiLjhrtOVLQBE2yK0A7Wwdh9KJg3QgNqwtFrR1MA1LgWto1F7NyEMC
+9H6Hc95+bgWx1jN0IflfPh1C1m/sA5xGqHDl+8YzJJUOoa5bh04Yk3aIeecatso/
+z7P5c6KicPcZIjhgjxHYB95It/oj8ZuY0hQZb7B5HEGNyBbT2F0vuElWtp+mXexr
+6mzgzvHgaKG36bNCTLxr8BxGA/sbVn01LyI3wpk2uqWzyUFk21M4g2X46OPgKrh7
+2h5b+C0X8DUPi45djHcCAwEAAaOBvDCBuTAdBgNVHQ4EFgQUKcrfRFg+6o2l4xbt
+Ll6hV9pjJh8wgYkGA1UdIwSBgTB/gBQpyt9EWD7qjaXjFu0uXqFX2mMmH6FcpFow
+WDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1TYW4gRnJhbmNp
+c2NvMQ8wDQYDVQQKEwZEb2NrZXIxEzARBgNVBAMTCmRlbGVnYXRpb26CCQCig0TK
+68gUSjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAhdKgYUQ36JSPS
+f3Dws09pM5hzSsSae4+nG9XckX6dVQ7sLKmjeeeLrXuzjfygir/+h9cHyShgXFH4
+ZbGpdzf6APG1KRag3/njqEWi+kKZZduxZKvI2EHJhj1xBtf8Qru0TgS7bHPlp9bl
+1/61+aIrtj05LQhqzWzehuJFrmSdWP9cnNbvlPdOdgfgkKakAiLGwwGNvMQbqxaO
+FIB4UPuPdQgm5bpimd5/CThKbpK9/0nr9K4po/m519nvEKxZzsDw5tefGp9Xqly3
+4pk9uyAxO/E2cL0cVA/WHTVTsHPbO7lXxBi6/EjiTUi0Nj1X+btO8+jCLkJyNY0m
+qaiL5k9h
+-----END CERTIFICATE-----

+ 27 - 0
integration-cli/fixtures/notary/delgkey4.key

@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA4J9Z99/mZf+Sj9jv3Q7Th5HuHz/hi1lTcsUi1yOGSmEx/ZXY
+FGWFbDBbq/3qBM3XGxnWKXr9heO+JaIuOGu05UtAETbIrQDtbB2H0omDdCA2rC0W
+tHUwDUuBa2jUXs3IQwL0fodz3n5uBbHWM3Qh+V8+HULWb+wDnEaocOX7xjMklQ6h
+rluHThiTdoh55xq2yj/Ps/lzoqJw9xkiOGCPEdgH3ki3+iPxm5jSFBlvsHkcQY3I
+FtPYXS+4SVa2n6Zd7GvqbODO8eBoobfps0JMvGvwHEYD+xtWfTUvIjfCmTa6pbPJ
+QWTbUziDZfjo4+AquHvaHlv4LRfwNQ+Ljl2MdwIDAQABAoIBAQCrN2wZsFZr2zK5
+aS/0/Y8poIe01Dm0wWMFcdULzm1XltzHIgfyDCx2ein2YPaXsNtNMhV3yuMiwqU3
+BHdc1GSv/vsX4/11Oea/6YaVafKEeuWRulC7PzRgffRpjh+LICqNQdxh8hfVOePd
+fV/8GoKnFf0/yqmv6GQcJBPS8stGmFmjo4rkBGvBBMoiUtMYllQqdfH0DtpI24Jh
+nR3lZKAPECkAciV7/Lx6+CUEaNOML2XPbLv6EyRh+J/r80jwE8myzpO7R6I+KCzo
+R/xuBb/hrUh5Sd5YmuBMa6WfF9yqawTgmVvkpD9fkRusSPSQCq3oe+AugYWu6Fht
+XBiZlvjJAoGBAPPBuUaagaUgHyjIzjbRPBHDhSYJpgYR4l/jcypfrl+m0OFC5acA
+QG7Hr8AbStIPGtaJRn2pm8dNVPtFecPoi5jVWux2n5RqYlOnwY0tziuxbhU9GQ/W
+oCp+99TJSMHFep0E7IoDk8YSxyA/86qk/Tx7KkUUlXv4sjJts17ZHxstAoGBAOvn
+mF9rm8Y+Og17WlUQyf5j7g4soWG/4zMnoGpjocDfHVms/pASKbIBp5aFtDgWCmM5
+H7InptvBUInROHlooK6paJRDLbDgzVa/m+NLHoct7N25J4NiG8xV6Wv7hlrRp+XK
+zyWL8iL95GnB21HJKvEiVBWvOuZnqfVcnzhbmzyzAoGAYT46jMkcyWRMKfgaFFJa
+lXebybX1rtw5pClYC2KKbQxerk8C0SHPkqJFIe2BZtWxzj6LiZw9UkAuk+N+lUJT
+VpBfKpCUTyA1w8vb8leAtXueQAjU07W6xdlLQ29dgDgpFzUcrF6K+G0LVXlN2xjh
+EdzM2yxACmoHpQiQk1kpCK0CgYAz640Fs1FdmGR+gx+miUNr0eKbDAeY0/rVT2tm
+/vai1HhJPGHqo5S5sNOJtXOsxG0U2YW4WDHJPArVyk57qiNzTaXOu9pai5+l8BYH
+OIlHhzwSsKWZrQYhOudc9MblRi+Fy9U7lkl8mhSjkh8LKRNibwPCogZ8n2QwtGn2
+pXLNMQKBgQDxvs46CA0M9lGvpl0ggnC7bIYYUEvIlszlBh+o2CgF3IOFlGVcvCia
+r18i7hTM5wbcct9OWDzZG4ejBIhtE+gMQ333ofQ64PPJOcfuHxT3Z/fMWfv/yDEj
+4e4ZPK44ktcTvuusxAoSe5C5dbcNX2ymAhlRg/F0LyMkhw+qGh4xOQ==
+-----END RSA PRIVATE KEY-----

+ 2 - 2
integration-cli/requirements.go

@@ -92,10 +92,10 @@ var (
 			// for now notary binary is built only if we're running inside
 			// container through `make test`. Figure that out by testing if
 			// notary-server binary is in PATH.
-			_, err := exec.LookPath(notaryBinary)
+			_, err := exec.LookPath(notaryServerBinary)
 			return err == nil
 		},
-		fmt.Sprintf("Test requires an environment that can host %s in the same host", notaryBinary),
+		fmt.Sprintf("Test requires an environment that can host %s in the same host", notaryServerBinary),
 	}
 	NotaryServerHosting = testRequirement{
 		func() bool {

+ 89 - 20
integration-cli/trust_server.go

@@ -12,19 +12,23 @@ import (
 	"time"
 
 	"github.com/docker/docker/cliconfig"
+	"github.com/docker/docker/pkg/integration/checker"
 	"github.com/docker/docker/pkg/tlsconfig"
-	"github.com/docker/notary/client"
-	"github.com/docker/notary/passphrase"
-	"github.com/docker/notary/tuf/data"
 	"github.com/go-check/check"
 )
 
 var notaryBinary = "notary"
 var notaryServerBinary = "notary-server"
 
+type keyPair struct {
+	Public  string
+	Private string
+}
+
 type testNotary struct {
-	cmd *exec.Cmd
-	dir string
+	cmd  *exec.Cmd
+	dir  string
+	keys []keyPair
 }
 
 const notaryHost = "localhost:4443"
@@ -90,6 +94,15 @@ func newTestNotary(c *check.C) (*testNotary, error) {
 		return nil, err
 	}
 
+	// load key fixture filenames
+	var keys []keyPair
+	for i := 1; i < 5; i++ {
+		keys = append(keys, keyPair{
+			Public:  filepath.Join(workingDir, fmt.Sprintf("fixtures/notary/delgkey%v.crt", i)),
+			Private: filepath.Join(workingDir, fmt.Sprintf("fixtures/notary/delgkey%v.key", i)),
+		})
+	}
+
 	// run notary-server
 	cmd := exec.Command(notaryServerBinary, "-config", confPath)
 	if err := cmd.Start(); err != nil {
@@ -101,8 +114,9 @@ func newTestNotary(c *check.C) (*testNotary, error) {
 	}
 
 	testNotary := &testNotary{
-		cmd: cmd,
-		dir: tmp,
+		cmd:  cmd,
+		dir:  tmp,
+		keys: keys,
 	}
 
 	// Wait for notary to be ready to serve requests.
@@ -220,33 +234,88 @@ func notaryClientEnv(cmd *exec.Cmd, rootPwd, repositoryPwd string) {
 	cmd.Env = append(os.Environ(), env...)
 }
 
-func (s *DockerTrustSuite) setupDelegations(c *check.C, repoName, pwd string) {
+func (s *DockerTrustSuite) notaryInitRepo(c *check.C, repoName, pwd string) {
 	initCmd := exec.Command(notaryBinary, "-c", filepath.Join(s.not.dir, "client-config.json"), "init", repoName)
 	notaryClientEnv(initCmd, pwd, pwd)
 	out, _, err := runCommandWithOutput(initCmd)
 	if err != nil {
 		c.Fatalf("Error initializing notary repository: %s\n", out)
 	}
+}
 
-	// no command line for this, so build by hand
-	nRepo, err := client.NewNotaryRepository(filepath.Join(cliconfig.ConfigDir(), "trust"), repoName, notaryURL, nil, passphrase.ConstantRetriever(pwd))
-	if err != nil {
-		c.Fatalf("Error creating notary repository: %s\n", err)
-	}
-	delgKey, err := nRepo.CryptoService.Create("targets/releases", data.ECDSAKey)
-	if err != nil {
-		c.Fatalf("Error creating delegation key: %s\n", err)
+func (s *DockerTrustSuite) notaryCreateDelegation(c *check.C, repoName, pwd, role string, pubKey string, paths ...string) {
+	pathsArg := "--all-paths"
+	if len(paths) > 0 {
+		pathsArg = "--paths=" + strings.Join(paths, ",")
 	}
-	err = nRepo.AddDelegation("targets/releases", []data.PublicKey{delgKey}, []string{""})
+
+	delgCmd := exec.Command(notaryBinary, "-c", filepath.Join(s.not.dir, "client-config.json"),
+		"delegation", "add", repoName, role, pubKey, pathsArg)
+	notaryClientEnv(delgCmd, pwd, pwd)
+	out, _, err := runCommandWithOutput(delgCmd)
 	if err != nil {
-		c.Fatalf("Error creating delegation: %s\n", err)
+		c.Fatalf("Error adding %s role to notary repository: %s\n", role, out)
 	}
+}
 
-	// publishing first simulates the client pushing to a repo that they have been given delegated access to
+func (s *DockerTrustSuite) notaryPublish(c *check.C, repoName, pwd string) {
 	pubCmd := exec.Command(notaryBinary, "-c", filepath.Join(s.not.dir, "client-config.json"), "publish", repoName)
 	notaryClientEnv(pubCmd, pwd, pwd)
-	out, _, err = runCommandWithOutput(pubCmd)
+	out, _, err := runCommandWithOutput(pubCmd)
 	if err != nil {
 		c.Fatalf("Error publishing notary repository: %s\n", out)
 	}
 }
+
+func (s *DockerTrustSuite) notaryImportKey(c *check.C, repoName, role string, privKey string) {
+	impCmd := exec.Command(notaryBinary, "-c", filepath.Join(s.not.dir, "client-config.json"), "key",
+		"import", privKey, "-g", repoName, "-r", role)
+	notaryClientEnv(impCmd, "", "")
+	out, _, err := runCommandWithOutput(impCmd)
+	if err != nil {
+		c.Fatalf("Error importing key to notary repository: %s\n", out)
+	}
+}
+
+func (s *DockerTrustSuite) notaryListTargetsInRoles(c *check.C, repoName, role string) map[string]string {
+	listCmd := exec.Command(notaryBinary, "-c", filepath.Join(s.not.dir, "client-config.json"), "list",
+		repoName, "-r", role)
+	notaryClientEnv(listCmd, "", "")
+	out, _, err := runCommandWithOutput(listCmd)
+	if err != nil {
+		c.Fatalf("Error importing key to notary repository: %s\n", out)
+	}
+
+	// should look something like:
+	//    NAME                                 DIGEST                                SIZE (BYTES)    ROLE
+	// ------------------------------------------------------------------------------------------------------
+	//   latest   24a36bbc059b1345b7e8be0df20f1b23caa3602e85d42fff7ecd9d0bd255de56   1377           targets
+
+	lines := strings.Split(strings.TrimSpace(out), "\n")
+	c.Assert(len(lines), checker.GreaterOrEqualThan, 3)
+	targets := make(map[string]string)
+
+	for _, line := range lines[2:] {
+		tokens := strings.Fields(line)
+		c.Assert(tokens, checker.HasLen, 4)
+		targets[tokens[0]] = tokens[3]
+	}
+
+	return targets
+}
+
+func (s *DockerTrustSuite) assertTargetInDelegationRoles(c *check.C, repoName, target string, roles ...string) {
+	// assert it's not in the target role
+	targets := s.notaryListTargetsInRoles(c, repoName, "targets")
+	roleName, ok := targets[target]
+	c.Assert(ok, checker.True)
+	c.Assert(roleName, checker.Not(checker.Equals), "targets")
+
+	// check all the roles
+	for _, role := range roles {
+		targets := s.notaryListTargetsInRoles(c, repoName, role)
+		roleName, ok := targets[target]
+		c.Assert(ok, checker.True)
+		c.Assert(roleName, checker.Equals, role)
+	}
+}