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