mirror of https://github.com/docker/docs.git
				
				
				
			Merge pull request #49 from docker/key_locations
Key locations Signed-off-by: David Lawrence <david.lawrence@docker.com> Signed-off-by: David Lawrence <dclwrnc@gmail.com> (github: endophage)
This commit is contained in:
		
						commit
						189118164d
					
				|  | @ -23,6 +23,7 @@ import ( | |||
| 	"github.com/docker/notary/server" | ||||
| 	"github.com/docker/notary/server/storage" | ||||
| 	"github.com/docker/notary/trustmanager" | ||||
| 	"github.com/docker/notary/tuf/data" | ||||
| 	"github.com/spf13/cobra" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| 	"golang.org/x/net/context" | ||||
|  | @ -50,10 +51,7 @@ func setupServer() *httptest.Server { | |||
| 	ctx := context.WithValue( | ||||
| 		context.Background(), "metaStore", storage.NewMemStorage()) | ||||
| 
 | ||||
| 	// Do not pass one of the const KeyAlgorithms here as the value! Passing a
 | ||||
| 	// string is in itself good test that we are handling it correctly as we
 | ||||
| 	// will be receiving a string from the configuration.
 | ||||
| 	ctx = context.WithValue(ctx, "keyAlgorithm", "ecdsa") | ||||
| 	ctx = context.WithValue(ctx, "keyAlgorithm", data.ECDSAKey) | ||||
| 
 | ||||
| 	// Eat the logs instead of spewing them out
 | ||||
| 	var b bytes.Buffer | ||||
|  | @ -181,19 +179,24 @@ func GetKeys(t *testing.T, tempDir string) ([]string, []string) { | |||
| func assertNumKeys(t *testing.T, tempDir string, numRoot, numSigning int, | ||||
| 	rootOnDisk bool) ([]string, []string) { | ||||
| 
 | ||||
| 	uniqueKeys := make(map[string]struct{}) | ||||
| 	root, signing := GetKeys(t, tempDir) | ||||
| 	assert.Len(t, root, numRoot) | ||||
| 	assert.Len(t, signing, numSigning) | ||||
| 	for _, rootKeyID := range root { | ||||
| 	for i, rootKeyLine := range root { | ||||
| 		keyID := strings.Split(rootKeyLine, "-")[0] | ||||
| 		keyID = strings.TrimSpace(keyID) | ||||
| 		root[i] = keyID | ||||
| 		uniqueKeys[keyID] = struct{}{} | ||||
| 		_, err := os.Stat(filepath.Join( | ||||
| 			tempDir, "private", "root_keys", rootKeyID+"_root.key")) | ||||
| 			tempDir, "private", "root_keys", keyID+"_root.key")) | ||||
| 		// os.IsExist checks to see if the error is because a file already
 | ||||
| 		// exist, and hence doesn't actually the right funciton to use here
 | ||||
| 		assert.Equal(t, rootOnDisk, !os.IsNotExist(err)) | ||||
| 
 | ||||
| 		// this function is declared is in the build-tagged setup files
 | ||||
| 		verifyRootKeyOnHardware(t, rootKeyID) | ||||
| 		verifyRootKeyOnHardware(t, keyID) | ||||
| 	} | ||||
| 	assert.Len(t, uniqueKeys, numRoot) | ||||
| 	return root, signing | ||||
| } | ||||
| 
 | ||||
|  | @ -245,7 +248,7 @@ func TestClientKeyGenerationRotation(t *testing.T) { | |||
| 	assertNumKeys(t, tempDir, 0, 0, true) | ||||
| 
 | ||||
| 	// generate root key produces a single root key and no other keys
 | ||||
| 	_, err = runCommand(t, tempDir, "key", "generate", "ecdsa") | ||||
| 	_, err = runCommand(t, tempDir, "key", "generate", data.ECDSAKey) | ||||
| 	assert.NoError(t, err) | ||||
| 	assertNumKeys(t, tempDir, 1, 0, true) | ||||
| 
 | ||||
|  | @ -345,7 +348,7 @@ func TestClientKeyImportExportRootAndSigning(t *testing.T) { | |||
| 
 | ||||
| 	_, err = runCommand(t, dirs[1], "key", "import", zipfile) | ||||
| 	assert.NoError(t, err) | ||||
| 	assertNumKeys(t, dirs[1], 1, 4, true) // all keys should be there
 | ||||
| 	assertNumKeys(t, dirs[1], 1, 4, !rootOnHardware()) // all keys should be there
 | ||||
| 
 | ||||
| 	// can list and publish to both repos using imported keys
 | ||||
| 	for _, gun := range []string{"gun1", "gun2"} { | ||||
|  | @ -383,7 +386,7 @@ func exportRoot(t *testing.T, exportTo string) string { | |||
| 	defer os.RemoveAll(tempDir) | ||||
| 
 | ||||
| 	// generate root key produces a single root key and no other keys
 | ||||
| 	_, err = runCommand(t, tempDir, "key", "generate", "ecdsa") | ||||
| 	_, err = runCommand(t, tempDir, "key", "generate", data.ECDSAKey) | ||||
| 	assert.NoError(t, err) | ||||
| 	oldRoot, _ := assertNumKeys(t, tempDir, 1, 0, true) | ||||
| 
 | ||||
|  | @ -505,7 +508,6 @@ func TestClientCertInteraction(t *testing.T) { | |||
| 	_, err = runCommand(t, tempDir, "cert", "remove", certID, "-y", "-g", "") | ||||
| 	assert.NoError(t, err) | ||||
| 	assertNumCerts(t, tempDir, 0) | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| func TestMain(m *testing.M) { | ||||
|  |  | |||
|  | @ -7,7 +7,11 @@ import ( | |||
| 	"sort" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/docker/notary" | ||||
| 	notaryclient "github.com/docker/notary/client" | ||||
| 	"github.com/docker/notary/cryptoservice" | ||||
| 	"github.com/docker/notary/passphrase" | ||||
| 	"github.com/docker/notary/trustmanager" | ||||
| 
 | ||||
| 	"github.com/docker/notary/tuf/data" | ||||
| 	"github.com/spf13/cobra" | ||||
|  | @ -93,16 +97,20 @@ func keysList(cmd *cobra.Command, args []string) { | |||
| 
 | ||||
| 	parseConfig() | ||||
| 
 | ||||
| 	cs := getCryptoService(cmd, trustDir, retriever, true) | ||||
| 	stores := getKeyStores(cmd, trustDir, retriever, true) | ||||
| 
 | ||||
| 	// Get a map of all the keys/roles
 | ||||
| 	keysMap := cs.ListAllKeys() | ||||
| 	keys := make(map[trustmanager.KeyStore]map[string]string) | ||||
| 	for _, store := range stores { | ||||
| 		keys[store] = store.ListKeys() | ||||
| 	} | ||||
| 
 | ||||
| 	cmd.Println("") | ||||
| 	cmd.Println("# Root keys: ") | ||||
| 	for k, v := range keysMap { | ||||
| 		if v == "root" { | ||||
| 			cmd.Println(k) | ||||
| 	for store, keysMap := range keys { | ||||
| 		for k, v := range keysMap { | ||||
| 			if v == "root" { | ||||
| 				cmd.Println(k, "-", store.Name()) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|  | @ -110,17 +118,20 @@ func keysList(cmd *cobra.Command, args []string) { | |||
| 	cmd.Println("# Signing keys: ") | ||||
| 
 | ||||
| 	// Get a list of all the keys
 | ||||
| 	var sortedKeys []string | ||||
| 	for k := range keysMap { | ||||
| 		sortedKeys = append(sortedKeys, k) | ||||
| 	} | ||||
| 	// Sort the list of all the keys
 | ||||
| 	sort.Strings(sortedKeys) | ||||
| 	for store, keysMap := range keys { | ||||
| 		var sortedKeys []string | ||||
| 		for k := range keysMap { | ||||
| 			sortedKeys = append(sortedKeys, k) | ||||
| 		} | ||||
| 
 | ||||
| 	// Print a sorted list of the key/role
 | ||||
| 	for _, k := range sortedKeys { | ||||
| 		if keysMap[k] != "root" { | ||||
| 			printKey(cmd, k, keysMap[k]) | ||||
| 		// Sort the list of all the keys
 | ||||
| 		sort.Strings(sortedKeys) | ||||
| 
 | ||||
| 		// Print a sorted list of the key/role
 | ||||
| 		for _, k := range sortedKeys { | ||||
| 			if keysMap[k] != "root" { | ||||
| 				printKey(cmd, k, keysMap[k], store.Name()) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -143,7 +154,10 @@ func keysGenerateRootKey(cmd *cobra.Command, args []string) { | |||
| 
 | ||||
| 	parseConfig() | ||||
| 
 | ||||
| 	cs := getCryptoService(cmd, trustDir, retriever, true) | ||||
| 	cs := cryptoservice.NewCryptoService( | ||||
| 		"", | ||||
| 		getKeyStores(cmd, trustDir, retriever, true)..., | ||||
| 	) | ||||
| 
 | ||||
| 	pubKey, err := cs.Create(data.CanonicalRootRole, algorithm) | ||||
| 	if err != nil { | ||||
|  | @ -164,7 +178,10 @@ func keysExport(cmd *cobra.Command, args []string) { | |||
| 
 | ||||
| 	parseConfig() | ||||
| 
 | ||||
| 	cs := getCryptoService(cmd, trustDir, retriever, false) | ||||
| 	cs := cryptoservice.NewCryptoService( | ||||
| 		"", | ||||
| 		getKeyStores(cmd, trustDir, retriever, true)..., | ||||
| 	) | ||||
| 
 | ||||
| 	exportFile, err := os.Create(exportFilename) | ||||
| 	if err != nil { | ||||
|  | @ -204,7 +221,10 @@ func keysExportRoot(cmd *cobra.Command, args []string) { | |||
| 
 | ||||
| 	parseConfig() | ||||
| 
 | ||||
| 	cs := getCryptoService(cmd, trustDir, retriever, false) | ||||
| 	cs := cryptoservice.NewCryptoService( | ||||
| 		"", | ||||
| 		getKeyStores(cmd, trustDir, retriever, true)..., | ||||
| 	) | ||||
| 
 | ||||
| 	exportFile, err := os.Create(exportFilename) | ||||
| 	if err != nil { | ||||
|  | @ -236,7 +256,10 @@ func keysImport(cmd *cobra.Command, args []string) { | |||
| 
 | ||||
| 	parseConfig() | ||||
| 
 | ||||
| 	cs := getCryptoService(cmd, trustDir, retriever, false) | ||||
| 	cs := cryptoservice.NewCryptoService( | ||||
| 		"", | ||||
| 		getKeyStores(cmd, trustDir, retriever, true)..., | ||||
| 	) | ||||
| 
 | ||||
| 	zipReader, err := zip.OpenReader(importFilename) | ||||
| 	if err != nil { | ||||
|  | @ -262,7 +285,10 @@ func keysImportRoot(cmd *cobra.Command, args []string) { | |||
| 
 | ||||
| 	parseConfig() | ||||
| 
 | ||||
| 	cs := getCryptoService(cmd, trustDir, retriever, true) | ||||
| 	cs := cryptoservice.NewCryptoService( | ||||
| 		"", | ||||
| 		getKeyStores(cmd, trustDir, retriever, true)..., | ||||
| 	) | ||||
| 
 | ||||
| 	importFile, err := os.Open(importFilename) | ||||
| 	if err != nil { | ||||
|  | @ -277,10 +303,10 @@ func keysImportRoot(cmd *cobra.Command, args []string) { | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| func printKey(cmd *cobra.Command, keyPath, alias string) { | ||||
| func printKey(cmd *cobra.Command, keyPath, alias, loc string) { | ||||
| 	keyID := filepath.Base(keyPath) | ||||
| 	gun := filepath.Dir(keyPath) | ||||
| 	cmd.Printf("%s - %s - %s\n", gun, alias, keyID) | ||||
| 	cmd.Printf("%s - %s - %s - %s\n", gun, alias, keyID, loc) | ||||
| } | ||||
| 
 | ||||
| func keysRotate(cmd *cobra.Command, args []string) { | ||||
|  | @ -299,3 +325,26 @@ func keysRotate(cmd *cobra.Command, args []string) { | |||
| 		fatalf(err.Error()) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func getKeyStores(cmd *cobra.Command, directory string, | ||||
| 	ret passphrase.Retriever, withHardware bool) []trustmanager.KeyStore { | ||||
| 
 | ||||
| 	keysPath := filepath.Join(directory, notary.PrivDir) | ||||
| 	fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, ret) | ||||
| 	if err != nil { | ||||
| 		fatalf("Failed to create private key store in directory: %s", keysPath) | ||||
| 	} | ||||
| 
 | ||||
| 	ks := []trustmanager.KeyStore{fileKeyStore} | ||||
| 
 | ||||
| 	if withHardware { | ||||
| 		yubiStore, err := getYubiKeyStore(fileKeyStore, ret) | ||||
| 		if err == nil && yubiStore != nil { | ||||
| 			// Note that the order is important, since we want to prioritize
 | ||||
| 			// the yubikey store
 | ||||
| 			ks = []trustmanager.KeyStore{yubiStore, fileKeyStore} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return ks | ||||
| } | ||||
|  |  | |||
|  | @ -3,22 +3,12 @@ | |||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"path/filepath" | ||||
| 	"errors" | ||||
| 
 | ||||
| 	"github.com/docker/notary" | ||||
| 	"github.com/docker/notary/cryptoservice" | ||||
| 	"github.com/docker/notary/passphrase" | ||||
| 	"github.com/docker/notary/trustmanager" | ||||
| 	"github.com/spf13/cobra" | ||||
| ) | ||||
| 
 | ||||
| func getCryptoService(cmd *cobra.Command, directory string, | ||||
| 	ret passphrase.Retriever, _ bool) *cryptoservice.CryptoService { | ||||
| 
 | ||||
| 	keysPath := filepath.Join(directory, notary.PrivDir) | ||||
| 	fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, ret) | ||||
| 	if err != nil { | ||||
| 		fatalf("Failed to create private key store in directory: %s", keysPath) | ||||
| 	} | ||||
| 	return cryptoservice.NewCryptoService("", fileKeyStore) | ||||
| func getYubiKeyStore(fileKeyStore trustmanager.KeyStore, ret passphrase.Retriever) (trustmanager.KeyStore, error) { | ||||
| 	return nil, errors.New("Not built with hardware support") | ||||
| } | ||||
|  |  | |||
|  | @ -3,38 +3,10 @@ | |||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"path/filepath" | ||||
| 
 | ||||
| 	"github.com/docker/notary" | ||||
| 	"github.com/docker/notary/cryptoservice" | ||||
| 	"github.com/docker/notary/passphrase" | ||||
| 	"github.com/docker/notary/trustmanager" | ||||
| 	"github.com/spf13/cobra" | ||||
| ) | ||||
| 
 | ||||
| // Build a CryptoService, optionally including a hardware keystore.  Returns
 | ||||
| // the CryptoService and whether or not a hardware keystore was included.
 | ||||
| func getCryptoService(cmd *cobra.Command, directory string, | ||||
| 	ret passphrase.Retriever, withHardware bool) *cryptoservice.CryptoService { | ||||
| 
 | ||||
| 	keysPath := filepath.Join(directory, notary.PrivDir) | ||||
| 	fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, ret) | ||||
| 	if err != nil { | ||||
| 		fatalf("Failed to create private key store in directory: %s", keysPath) | ||||
| 	} | ||||
| 
 | ||||
| 	ks := []trustmanager.KeyStore{fileKeyStore} | ||||
| 
 | ||||
| 	if withHardware { | ||||
| 		yubiStore, err := trustmanager.NewYubiKeyStore(fileKeyStore, ret) | ||||
| 		if err != nil { | ||||
| 			cmd.Println("No YubiKey detected - using local filesystem only.") | ||||
| 		} else { | ||||
| 			// Note that the order is important, since we want to prioritize
 | ||||
| 			// the yubikey store
 | ||||
| 			ks = []trustmanager.KeyStore{yubiStore, fileKeyStore} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return cryptoservice.NewCryptoService("", ks...) | ||||
| func getYubiKeyStore(fileKeyStore trustmanager.KeyStore, ret passphrase.Retriever) (trustmanager.KeyStore, error) { | ||||
| 	return trustmanager.NewYubiKeyStore(fileKeyStore, ret) | ||||
| } | ||||
|  |  | |||
|  | @ -197,11 +197,20 @@ func (cs *CryptoService) ImportKeysZip(zipReader zip.Reader) error { | |||
| 		if keyName[len(keyName)-5:] == "_root" { | ||||
| 			keyName = "root" | ||||
| 		} | ||||
| 		// try to import the key to all key stores. As long as one of them
 | ||||
| 		// succeeds, consider it a success
 | ||||
| 		var tmpErr error | ||||
| 		for _, ks := range cs.keyStores { | ||||
| 			if err := ks.ImportKey(pemBytes, keyName); err != nil { | ||||
| 				return err | ||||
| 				tmpErr = err | ||||
| 			} else { | ||||
| 				tmpErr = nil | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 		if tmpErr != nil { | ||||
| 			return tmpErr | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
|  |  | |||
|  | @ -58,6 +58,11 @@ func NewKeyDBStore(passphraseRetriever passphrase.Retriever, defaultPassAlias, d | |||
| 		cachedKeys:       cachedKeys}, nil | ||||
| } | ||||
| 
 | ||||
| // Name returns a user friendly name for the storage location
 | ||||
| func (s *KeyDBStore) Name() string { | ||||
| 	return "database" | ||||
| } | ||||
| 
 | ||||
| // AddKey stores the contents of a private key. Both name and alias are ignored,
 | ||||
| // we always use Key IDs as name, and don't support aliases
 | ||||
| func (s *KeyDBStore) AddKey(name, alias string, privKey data.PrivateKey) error { | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| package trustmanager | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
|  | @ -44,6 +45,12 @@ func NewKeyFileStore(baseDir string, passphraseRetriever passphrase.Retriever) ( | |||
| 		cachedKeys: cachedKeys}, nil | ||||
| } | ||||
| 
 | ||||
| // Name returns a user friendly name for the location this store
 | ||||
| // keeps its data
 | ||||
| func (s *KeyFileStore) Name() string { | ||||
| 	return fmt.Sprintf("file (%s)", s.SimpleFileStore.BaseDir()) | ||||
| } | ||||
| 
 | ||||
| // AddKey stores the contents of a PEM-encoded private key as a PEM block
 | ||||
| func (s *KeyFileStore) AddKey(name, alias string, privKey data.PrivateKey) error { | ||||
| 	s.Lock() | ||||
|  | @ -96,6 +103,12 @@ func NewKeyMemoryStore(passphraseRetriever passphrase.Retriever) *KeyMemoryStore | |||
| 		cachedKeys: cachedKeys} | ||||
| } | ||||
| 
 | ||||
| // Name returns a user friendly name for the location this store
 | ||||
| // keeps its data
 | ||||
| func (s *KeyMemoryStore) Name() string { | ||||
| 	return "memory" | ||||
| } | ||||
| 
 | ||||
| // AddKey stores the contents of a PEM-encoded private key as a PEM block
 | ||||
| func (s *KeyMemoryStore) AddKey(name, alias string, privKey data.PrivateKey) error { | ||||
| 	s.Lock() | ||||
|  |  | |||
|  | @ -46,6 +46,7 @@ type KeyStore interface { | |||
| 	RemoveKey(name string) error | ||||
| 	ExportKey(name string) ([]byte, error) | ||||
| 	ImportKey(pemBytes []byte, alias string) error | ||||
| 	Name() string | ||||
| } | ||||
| 
 | ||||
| type cachedKey struct { | ||||
|  |  | |||
|  | @ -398,12 +398,12 @@ func ED25519ToPrivateKey(privKeyBytes []byte) (data.PrivateKey, error) { | |||
| } | ||||
| 
 | ||||
| func blockType(k data.PrivateKey) (string, error) { | ||||
| 	switch k.(type) { | ||||
| 	case *data.RSAPrivateKey: | ||||
| 	switch k.Algorithm() { | ||||
| 	case data.RSAKey, data.RSAx509Key: | ||||
| 		return "RSA PRIVATE KEY", nil | ||||
| 	case *data.ECDSAPrivateKey: | ||||
| 	case data.ECDSAKey, data.ECDSAx509Key: | ||||
| 		return "EC PRIVATE KEY", nil | ||||
| 	case *data.ED25519PrivateKey: | ||||
| 	case data.ED25519Key: | ||||
| 		return "ED25519 PRIVATE KEY", nil | ||||
| 	default: | ||||
| 		return "", fmt.Errorf("algorithm %s not supported", k.Algorithm()) | ||||
|  |  | |||
|  | @ -580,6 +580,12 @@ func NewYubiKeyStore(backupStore KeyStore, passphraseRetriever passphrase.Retrie | |||
| 	return s, nil | ||||
| } | ||||
| 
 | ||||
| // Name returns a user friendly name for the location this store
 | ||||
| // keeps its data
 | ||||
| func (s YubiKeyStore) Name() string { | ||||
| 	return "yubikey" | ||||
| } | ||||
| 
 | ||||
| func (s *YubiKeyStore) ListKeys() map[string]string { | ||||
| 	if len(s.keys) > 0 { | ||||
| 		return buildKeyMap(s.keys) | ||||
|  | @ -701,13 +707,16 @@ func (s *YubiKeyStore) ExportKey(keyID string) ([]byte, error) { | |||
| } | ||||
| 
 | ||||
| // ImportKey imports a root key into a Yubikey
 | ||||
| func (s *YubiKeyStore) ImportKey(pemBytes []byte, keyID string) error { | ||||
| 	logrus.Debugf("Attempting to import: %s key inside of YubiKeyStore", keyID) | ||||
| func (s *YubiKeyStore) ImportKey(pemBytes []byte, keyPath string) error { | ||||
| 	logrus.Debugf("Attempting to import: %s key inside of YubiKeyStore", keyPath) | ||||
| 	privKey, _, err := GetPasswdDecryptBytes( | ||||
| 		s.passRetriever, pemBytes, "", "imported root") | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if keyPath != data.CanonicalRootRole { | ||||
| 		return fmt.Errorf("yubikey only supports storing root keys") | ||||
| 	} | ||||
| 	return s.addKey(privKey.ID(), "root", privKey, false) | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -129,7 +129,7 @@ func TestImportKey(t *testing.T) { | |||
| 	pemBytes, err := EncryptPrivateKey(privKey, "passphrase") | ||||
| 	assert.NoError(t, err) | ||||
| 
 | ||||
| 	err = store.ImportKey(pemBytes, privKey.ID()) | ||||
| 	err = store.ImportKey(pemBytes, "root") | ||||
| 	assert.NoError(t, err) | ||||
| 
 | ||||
| 	// key is not in backup store
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue