mirror of https://github.com/docker/docs.git
Merge pull request #515 from docker/roles-for-targets
Roles for targets via notary CLI
This commit is contained in:
commit
564f8d06d3
|
@ -24,6 +24,7 @@ import (
|
||||||
"github.com/docker/notary/server/storage"
|
"github.com/docker/notary/server/storage"
|
||||||
"github.com/docker/notary/trustmanager"
|
"github.com/docker/notary/trustmanager"
|
||||||
"github.com/docker/notary/tuf/data"
|
"github.com/docker/notary/tuf/data"
|
||||||
|
"github.com/docker/notary/tuf/utils"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -50,6 +51,11 @@ func runCommand(t *testing.T, tempDir string, args ...string) (string, error) {
|
||||||
output, err := ioutil.ReadAll(b)
|
output, err := ioutil.ReadAll(b)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Clean up state to mimic running a fresh command next time
|
||||||
|
for _, command := range cmd.Commands() {
|
||||||
|
command.ResetFlags()
|
||||||
|
}
|
||||||
|
|
||||||
return string(output), retErr
|
return string(output), retErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -362,6 +368,164 @@ func TestClientDelegationsInteraction(t *testing.T) {
|
||||||
assert.Contains(t, output, "No delegations present in this repository.")
|
assert.Contains(t, output, "No delegations present in this repository.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize repo and test publishing targets with delegation roles
|
||||||
|
func TestClientDelegationsPublishing(t *testing.T) {
|
||||||
|
setUp(t)
|
||||||
|
|
||||||
|
tempDir := tempDirWithConfig(t, "{}")
|
||||||
|
defer os.RemoveAll(tempDir)
|
||||||
|
|
||||||
|
server := setupServer()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
// Setup certificate for delegation role
|
||||||
|
tempFile, err := ioutil.TempFile("/tmp", "pemfile")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
privKey, err := trustmanager.GenerateRSAKey(rand.Reader, 2048)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
privKeyBytesNoRole, err := trustmanager.KeyToPEM(privKey, "")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
privKeyBytesWithRole, err := trustmanager.KeyToPEM(privKey, "user")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
startTime := time.Now()
|
||||||
|
endTime := startTime.AddDate(10, 0, 0)
|
||||||
|
cert, err := cryptoservice.GenerateCertificate(privKey, "gun", startTime, endTime)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = tempFile.Write(trustmanager.CertToPEM(cert))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
tempFile.Close()
|
||||||
|
defer os.Remove(tempFile.Name())
|
||||||
|
|
||||||
|
rawPubBytes, _ := ioutil.ReadFile(tempFile.Name())
|
||||||
|
parsedPubKey, _ := trustmanager.ParsePEMPublicKey(rawPubBytes)
|
||||||
|
canonicalKeyID, err := utils.CanonicalKeyID(parsedPubKey)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Set up targets for publishing
|
||||||
|
tempTargetFile, err := ioutil.TempFile("/tmp", "targetfile")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
tempTargetFile.Close()
|
||||||
|
defer os.Remove(tempTargetFile.Name())
|
||||||
|
|
||||||
|
var target = "sdgkadga"
|
||||||
|
|
||||||
|
var output string
|
||||||
|
|
||||||
|
// init repo
|
||||||
|
_, err = runCommand(t, tempDir, "-s", server.URL, "init", "gun")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// publish repo
|
||||||
|
_, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// list delegations - none yet
|
||||||
|
output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Contains(t, output, "No delegations present in this repository.")
|
||||||
|
|
||||||
|
// publish repo
|
||||||
|
_, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// validate that we have all keys, including snapshot
|
||||||
|
assertNumKeys(t, tempDir, 1, 2, true)
|
||||||
|
|
||||||
|
// rotate the snapshot key to server
|
||||||
|
output, err = runCommand(t, tempDir, "-s", server.URL, "key", "rotate", "gun", "-r", "--key-type", "snapshot")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// publish repo
|
||||||
|
_, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// validate that we lost the snapshot signing key
|
||||||
|
_, signingKeyIDs := assertNumKeys(t, tempDir, 1, 1, true)
|
||||||
|
targetKeyID := signingKeyIDs[0]
|
||||||
|
|
||||||
|
// add new valid delegation with single new cert
|
||||||
|
output, err = runCommand(t, tempDir, "delegation", "add", "gun", "targets/releases", tempFile.Name(), "--paths", "\"\"")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Contains(t, output, "Addition of delegation role")
|
||||||
|
|
||||||
|
// publish repo
|
||||||
|
_, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// list delegations - we should see our one delegation
|
||||||
|
output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotContains(t, output, "No delegations present in this repository.")
|
||||||
|
|
||||||
|
// remove the targets key to demonstrate that delegates don't need this key
|
||||||
|
keyDir := filepath.Join(tempDir, "private", "tuf_keys")
|
||||||
|
assert.NoError(t, os.Remove(filepath.Join(keyDir, "gun", targetKeyID+".key")))
|
||||||
|
|
||||||
|
// Note that we need to use the canonical key ID, followed by the base of the role here
|
||||||
|
err = ioutil.WriteFile(filepath.Join(keyDir, canonicalKeyID+"_releases.key"), privKeyBytesNoRole, 0700)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// add a target using the delegation -- will only add to targets/releases
|
||||||
|
_, err = runCommand(t, tempDir, "add", "gun", target, tempTargetFile.Name(), "--roles", "targets/releases")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// list targets for targets/releases - we should see no targets until we publish
|
||||||
|
output, err = runCommand(t, tempDir, "-s", server.URL, "list", "gun", "--roles", "targets/releases")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Contains(t, output, "No targets")
|
||||||
|
|
||||||
|
output, err = runCommand(t, tempDir, "-s", server.URL, "status", "gun")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// publish repo
|
||||||
|
_, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// list targets for targets/releases - we should see our target!
|
||||||
|
output, err = runCommand(t, tempDir, "-s", server.URL, "list", "gun", "--roles", "targets/releases")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Contains(t, output, "targets/releases")
|
||||||
|
|
||||||
|
// remove the target for this role only
|
||||||
|
_, err = runCommand(t, tempDir, "remove", "gun", target, "--roles", "targets/releases")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// publish repo
|
||||||
|
_, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// list targets for targets/releases - we should see no targets
|
||||||
|
output, err = runCommand(t, tempDir, "-s", server.URL, "list", "gun", "--roles", "targets/releases")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Contains(t, output, "No targets present")
|
||||||
|
|
||||||
|
// Try adding a target with a different key style - private/tuf_keys/canonicalKeyID.key with "user" set as the "role" PEM header
|
||||||
|
// First remove the old key and add the new style
|
||||||
|
assert.NoError(t, os.Remove(filepath.Join(keyDir, canonicalKeyID+"_releases.key")))
|
||||||
|
err = ioutil.WriteFile(filepath.Join(keyDir, canonicalKeyID+".key"), privKeyBytesWithRole, 0700)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// add a target using the delegation -- will only add to targets/releases
|
||||||
|
_, err = runCommand(t, tempDir, "add", "gun", target, tempTargetFile.Name(), "--roles", "targets/releases")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// list targets for targets/releases - we should see no targets until we publish
|
||||||
|
output, err = runCommand(t, tempDir, "-s", server.URL, "list", "gun", "--roles", "targets/releases")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Contains(t, output, "No targets")
|
||||||
|
|
||||||
|
// publish repo
|
||||||
|
_, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// list targets for targets/releases - we should see our target!
|
||||||
|
output, err = runCommand(t, tempDir, "-s", server.URL, "list", "gun", "--roles", "targets/releases")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Contains(t, output, "targets/releases")
|
||||||
|
}
|
||||||
|
|
||||||
// Splits a string into lines, and returns any lines that are not empty (
|
// Splits a string into lines, and returns any lines that are not empty (
|
||||||
// striped of whitespace)
|
// striped of whitespace)
|
||||||
func splitLines(chunk string) []string {
|
func splitLines(chunk string) []string {
|
||||||
|
|
|
@ -22,6 +22,7 @@ const (
|
||||||
var (
|
var (
|
||||||
debug bool
|
debug bool
|
||||||
verbose bool
|
verbose bool
|
||||||
|
roles []string
|
||||||
trustDir string
|
trustDir string
|
||||||
configFile string
|
configFile string
|
||||||
remoteTrustServer string
|
remoteTrustServer string
|
||||||
|
@ -127,8 +128,11 @@ func setupCommand(notaryCmd *cobra.Command) {
|
||||||
notaryCmd.AddCommand(cmdDelegationGenerator.GetCommand())
|
notaryCmd.AddCommand(cmdDelegationGenerator.GetCommand())
|
||||||
notaryCmd.AddCommand(cmdCert)
|
notaryCmd.AddCommand(cmdCert)
|
||||||
notaryCmd.AddCommand(cmdTufInit)
|
notaryCmd.AddCommand(cmdTufInit)
|
||||||
|
cmdTufList.Flags().StringSliceVarP(&roles, "roles", "r", nil, "Delegation roles to list targets for (will shadow targets role)")
|
||||||
notaryCmd.AddCommand(cmdTufList)
|
notaryCmd.AddCommand(cmdTufList)
|
||||||
|
cmdTufAdd.Flags().StringSliceVarP(&roles, "roles", "r", nil, "Delegation roles to add this target to")
|
||||||
notaryCmd.AddCommand(cmdTufAdd)
|
notaryCmd.AddCommand(cmdTufAdd)
|
||||||
|
cmdTufRemove.Flags().StringSliceVarP(&roles, "roles", "r", nil, "Delegation roles to remove this target from")
|
||||||
notaryCmd.AddCommand(cmdTufRemove)
|
notaryCmd.AddCommand(cmdTufRemove)
|
||||||
notaryCmd.AddCommand(cmdTufStatus)
|
notaryCmd.AddCommand(cmdTufStatus)
|
||||||
notaryCmd.AddCommand(cmdTufPublish)
|
notaryCmd.AddCommand(cmdTufPublish)
|
||||||
|
|
|
@ -104,7 +104,8 @@ func tufAdd(cmd *cobra.Command, args []string) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf(err.Error())
|
fatalf(err.Error())
|
||||||
}
|
}
|
||||||
err = nRepo.AddTarget(target)
|
// If roles is empty, we default to adding to targets
|
||||||
|
err = nRepo.AddTarget(target, roles...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf(err.Error())
|
fatalf(err.Error())
|
||||||
}
|
}
|
||||||
|
@ -163,8 +164,9 @@ func tufList(cmd *cobra.Command, args []string) {
|
||||||
fatalf(err.Error())
|
fatalf(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retreive the remote list of signed targets
|
// Retrieve the remote list of signed targets, prioritizing the passed-in list over targets
|
||||||
targetList, err := nRepo.ListTargets(data.CanonicalTargetsRole, "targets/releases")
|
roles = append(roles, data.CanonicalTargetsRole)
|
||||||
|
targetList, err := nRepo.ListTargets(roles...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf(err.Error())
|
fatalf(err.Error())
|
||||||
}
|
}
|
||||||
|
@ -265,7 +267,8 @@ func tufRemove(cmd *cobra.Command, args []string) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf(err.Error())
|
fatalf(err.Error())
|
||||||
}
|
}
|
||||||
err = repo.RemoveTarget(targetName)
|
// If roles is empty, we default to removing from targets
|
||||||
|
err = repo.RemoveTarget(targetName, roles...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf(err.Error())
|
fatalf(err.Error())
|
||||||
}
|
}
|
||||||
|
|
|
@ -470,11 +470,16 @@ func KeyToPEM(privKey data.PrivateKey, role string) ([]byte, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
headers := map[string]string{}
|
||||||
|
if role != "" {
|
||||||
|
headers = map[string]string{
|
||||||
|
"role": role,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
block := &pem.Block{
|
block := &pem.Block{
|
||||||
Type: bt,
|
Type: bt,
|
||||||
Headers: map[string]string{
|
Headers: headers,
|
||||||
"role": role,
|
|
||||||
},
|
|
||||||
Bytes: privKey.Private(),
|
Bytes: privKey.Private(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue