Merge pull request #147 from docker/import-export-cli

Import export cli
This commit is contained in:
Diogo Mónica 2015-07-28 15:26:17 -07:00
commit e0e574f9ff
4 changed files with 310 additions and 12 deletions

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"archive/zip"
"crypto/x509" "crypto/x509"
"fmt" "fmt"
"math" "math"
@ -10,37 +11,84 @@ import (
"time" "time"
"github.com/docker/notary/keystoremanager" "github.com/docker/notary/keystoremanager"
"github.com/docker/notary/pkg/passphrase"
"github.com/docker/notary/trustmanager" "github.com/docker/notary/trustmanager"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func init() { func init() {
cmdKeys.AddCommand(cmdKeysRemoveRootKey) cmdKey.AddCommand(cmdKeyList)
cmdKeys.AddCommand(cmdKeysGenerateRootKey) cmdKey.AddCommand(cmdKeyRemoveRootKey)
cmdKey.AddCommand(cmdKeyGenerateRootKey)
cmdKeyExport.Flags().StringVarP(&keysExportGUN, "gun", "g", "", "Globally unique name to export keys for.")
cmdKey.AddCommand(cmdKeyExport)
cmdKey.AddCommand(cmdKeyExportRoot)
cmdKeyExportRoot.Flags().BoolVarP(&keysExportRootChangePassphrase, "change-passphrase", "c", false, "set a new passphrase for the key being exported")
cmdKey.AddCommand(cmdKeyImport)
cmdKey.AddCommand(cmdKeyImportRoot)
} }
var cmdKeys = &cobra.Command{ var cmdKey = &cobra.Command{
Use: "keys", Use: "key",
Short: "Operates on root keys.", Short: "Operates on keys.",
Long: "operations on private root keys.", Long: "operations on private keys.",
}
var cmdKeyList = &cobra.Command{
Use: "list",
Short: "Lists keys.",
Long: "lists keys known to notary.",
Run: keysList, Run: keysList,
} }
var cmdKeysRemoveRootKey = &cobra.Command{ var cmdKeyRemoveRootKey = &cobra.Command{
Use: "remove [ keyID ]", Use: "remove [ keyID ]",
Short: "Removes the root key with the given keyID.", Short: "Removes the root key with the given keyID.",
Long: "remove the root key with the given keyID from the local host.", Long: "remove the root key with the given keyID from the local host.",
Run: keysRemoveRootKey, Run: keysRemoveRootKey,
} }
var cmdKeysGenerateRootKey = &cobra.Command{ var cmdKeyGenerateRootKey = &cobra.Command{
Use: "generate [ algorithm ]", Use: "generate [ algorithm ]",
Short: "Generates a new root key with a given algorithm.", Short: "Generates a new root key with a given algorithm.",
Long: "generates a new root key with a given algorithm.", Long: "generates a new root key with a given algorithm.",
Run: keysGenerateRootKey, Run: keysGenerateRootKey,
} }
var keysExportGUN string
var cmdKeyExport = &cobra.Command{
Use: "export [ filename ]",
Short: "Exports keys to a ZIP file.",
Long: "exports a collection of keys. The keys are reencrypted with a new passphrase. The output is a ZIP file.",
Run: keysExport,
}
var keysExportRootChangePassphrase bool
var cmdKeyExportRoot = &cobra.Command{
Use: "export-root [ keyID ] [ filename ]",
Short: "Exports given root key to a file.",
Long: "exports a root key, without reencrypting. The output is a PEM file.",
Run: keysExportRoot,
}
var cmdKeyImport = &cobra.Command{
Use: "import [ filename ]",
Short: "Imports keys from a ZIP file.",
Long: "imports one or more keys from a ZIP file.",
Run: keysImport,
}
var cmdKeyImportRoot = &cobra.Command{
Use: "import-root [ keyID ] [ filename ]",
Short: "Imports root key.",
Long: "imports a root key from a PEM file.",
Run: keysImportRoot,
}
// keysRemoveRootKey deletes a root private key based on ID // keysRemoveRootKey deletes a root private key based on ID
func keysRemoveRootKey(cmd *cobra.Command, args []string) { func keysRemoveRootKey(cmd *cobra.Command, args []string) {
if len(args) < 1 { if len(args) < 1 {
@ -141,6 +189,147 @@ func keysGenerateRootKey(cmd *cobra.Command, args []string) {
fmt.Printf("Generated new %s key with keyID: %s\n", algorithm, keyID) fmt.Printf("Generated new %s key with keyID: %s\n", algorithm, keyID)
} }
// keysExport exports a collection of keys to a ZIP file
func keysExport(cmd *cobra.Command, args []string) {
if len(args) < 1 {
cmd.Usage()
fatalf("must specify output filename for export")
}
exportFilename := args[0]
parseConfig()
keyStoreManager, err := keystoremanager.NewKeyStoreManager(trustDir, retriever)
if err != nil {
fatalf("failed to create a new truststore manager with directory: %s", trustDir)
}
exportFile, err := os.Create(exportFilename)
if err != nil {
fatalf("error creating output file: %v", err)
}
// Must use a different passphrase retriever to avoid caching the
// unlocking passphrase and reusing that.
exportRetriever := passphrase.PromptRetriever()
if keysExportGUN != "" {
err = keyStoreManager.ExportKeysByGUN(exportFile, keysExportGUN, exportRetriever)
} else {
err = keyStoreManager.ExportAllKeys(exportFile, exportRetriever)
}
exportFile.Close()
if err != nil {
fatalf("error exporting keys: %v", err)
os.Remove(exportFilename)
}
}
// keysExportRoot exports a root key by ID to a PEM file
func keysExportRoot(cmd *cobra.Command, args []string) {
if len(args) < 2 {
cmd.Usage()
fatalf("must specify key ID and output filename for export")
}
keyID := args[0]
exportFilename := args[1]
if len(keyID) != 64 {
fatalf("please specify a valid root key ID")
}
parseConfig()
keyStoreManager, err := keystoremanager.NewKeyStoreManager(trustDir, retriever)
if err != nil {
fatalf("failed to create a new truststore manager with directory: %s", trustDir)
}
exportFile, err := os.Create(exportFilename)
if err != nil {
fatalf("error creating output file: %v", err)
}
if keysExportRootChangePassphrase {
// Must use a different passphrase retriever to avoid caching the
// unlocking passphrase and reusing that.
exportRetriever := passphrase.PromptRetriever()
err = keyStoreManager.ExportRootKeyReencrypt(exportFile, keyID, exportRetriever)
} else {
err = keyStoreManager.ExportRootKey(exportFile, keyID)
}
exportFile.Close()
if err != nil {
fatalf("error exporting root key: %v", err)
os.Remove(exportFilename)
}
}
// keysImport imports keys from a ZIP file
func keysImport(cmd *cobra.Command, args []string) {
if len(args) < 1 {
cmd.Usage()
fatalf("must specify input filename for import")
}
importFilename := args[0]
parseConfig()
keyStoreManager, err := keystoremanager.NewKeyStoreManager(trustDir, retriever)
if err != nil {
fatalf("failed to create a new truststore manager with directory: %s", trustDir)
}
zipReader, err := zip.OpenReader(importFilename)
if err != nil {
fatalf("opening file for import: %v", err)
}
defer zipReader.Close()
err = keyStoreManager.ImportKeysZip(zipReader.Reader)
if err != nil {
fatalf("error importing keys: %v", err)
}
}
// keysImportRoot imports a root key from a PEM file
func keysImportRoot(cmd *cobra.Command, args []string) {
if len(args) < 2 {
cmd.Usage()
fatalf("must specify key ID and input filename for import")
}
keyID := args[0]
importFilename := args[1]
if len(keyID) != 64 {
fatalf("please specify a valid root key ID")
}
parseConfig()
keyStoreManager, err := keystoremanager.NewKeyStoreManager(trustDir, retriever)
if err != nil {
fatalf("failed to create a new truststore manager with directory: %s", trustDir)
}
importFile, err := os.Open(importFilename)
if err != nil {
fatalf("opening file for import: %v", err)
}
defer importFile.Close()
err = keyStoreManager.ImportRootKey(importFile, keyID)
if err != nil {
fatalf("error importing root key: %v", err)
}
}
func printCert(cert *x509.Certificate) { func printCert(cert *x509.Certificate) {
timeDifference := cert.NotAfter.Sub(time.Now()) timeDifference := cert.NotAfter.Sub(time.Now())
certID, err := trustmanager.FingerprintCert(cert) certID, err := trustmanager.FingerprintCert(cert)

View File

@ -78,7 +78,7 @@ func main() {
NotaryCmd.PersistentFlags().StringVarP(&trustDir, "trustdir", "d", "", "directory where the trust data is persisted to") NotaryCmd.PersistentFlags().StringVarP(&trustDir, "trustdir", "d", "", "directory where the trust data is persisted to")
NotaryCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output") NotaryCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output")
NotaryCmd.AddCommand(cmdKeys) NotaryCmd.AddCommand(cmdKey)
NotaryCmd.AddCommand(cmdTufInit) NotaryCmd.AddCommand(cmdTufInit)
cmdTufInit.Flags().StringVarP(&remoteTrustServer, "server", "s", defaultServerURL, "Remote trust server location") cmdTufInit.Flags().StringVarP(&remoteTrustServer, "server", "s", defaultServerURL, "Remote trust server location")
NotaryCmd.AddCommand(cmdTufList) NotaryCmd.AddCommand(cmdTufList)

View File

@ -42,6 +42,39 @@ func (km *KeyStoreManager) ExportRootKey(dest io.Writer, keyID string) error {
return err return err
} }
// ExportRootKeyReencrypt exports the specified root key to an io.Writer in
// PEM format. The key is reencrypted with a new passphrase.
func (km *KeyStoreManager) ExportRootKeyReencrypt(dest io.Writer, keyID string, newPassphraseRetriever passphrase.Retriever) error {
privateKey, alias, err := km.rootKeyStore.GetKey(keyID)
if err != nil {
return err
}
// Create temporary keystore to use as a staging area
tempBaseDir, err := ioutil.TempDir("", "notary-key-export-")
defer os.RemoveAll(tempBaseDir)
privRootKeysSubdir := filepath.Join(privDir, rootKeysSubdir)
tempRootKeysPath := filepath.Join(tempBaseDir, privRootKeysSubdir)
tempRootKeyStore, err := trustmanager.NewKeyFileStore(tempRootKeysPath, newPassphraseRetriever)
if err != nil {
return err
}
err = tempRootKeyStore.AddKey(keyID, alias, privateKey)
if err != nil {
return err
}
pemBytes, err := tempRootKeyStore.Get(keyID + "_" + alias)
if err != nil {
return err
}
_, err = dest.Write(pemBytes)
return err
}
// checkRootKeyIsEncrypted makes sure the root key is encrypted. We have // checkRootKeyIsEncrypted makes sure the root key is encrypted. We have
// internal assumptions that depend on this. // internal assumptions that depend on this.
func checkRootKeyIsEncrypted(pemBytes []byte) error { func checkRootKeyIsEncrypted(pemBytes []byte) error {
@ -81,12 +114,12 @@ func (km *KeyStoreManager) ImportRootKey(source io.Reader, keyID string) error {
func moveKeys(oldKeyStore, newKeyStore *trustmanager.KeyFileStore) error { func moveKeys(oldKeyStore, newKeyStore *trustmanager.KeyFileStore) error {
// List all files but no symlinks // List all files but no symlinks
for _, f := range oldKeyStore.ListKeys() { for _, f := range oldKeyStore.ListKeys() {
pemBytes, alias, err := oldKeyStore.GetKey(f) privateKey, alias, err := oldKeyStore.GetKey(f)
if err != nil { if err != nil {
return err return err
} }
err = newKeyStore.AddKey(f, alias, pemBytes) err = newKeyStore.AddKey(f, alias, privateKey)
if err != nil { if err != nil {
return err return err

View File

@ -350,7 +350,6 @@ func TestImportExportRootKey(t *testing.T) {
err = repo2.Initialize(rootCryptoService2) err = repo2.Initialize(rootCryptoService2)
assert.NoError(t, err, "error creating repository: %s", err) assert.NoError(t, err, "error creating repository: %s", err)
// Check the contents of the zip file
keyReader, err := os.Open(tempKeyFilePath) keyReader, err := os.Open(tempKeyFilePath)
assert.NoError(t, err, "could not open key file") assert.NoError(t, err, "could not open key file")
@ -380,4 +379,81 @@ func TestImportExportRootKey(t *testing.T) {
// Try to import garbage and make sure it doesn't succeed // Try to import garbage and make sure it doesn't succeed
err = repo2.KeyStoreManager.ImportRootKey(strings.NewReader("this is not PEM"), rootKeyID) err = repo2.KeyStoreManager.ImportRootKey(strings.NewReader("this is not PEM"), rootKeyID)
assert.EqualError(t, err, keystoremanager.ErrNoValidPrivateKey.Error()) assert.EqualError(t, err, keystoremanager.ErrNoValidPrivateKey.Error())
// Should be able to unlock the root key with the old password
key, alias, err := repo2.KeyStoreManager.RootKeyStore().GetKey(rootKeyID)
assert.NoError(t, err, "could not unlock root key")
assert.Equal(t, "root", alias)
assert.Equal(t, rootKeyID, key.ID())
}
func TestImportExportRootKeyReencrypt(t *testing.T) {
gun := "docker.com/notary"
// Temporary directory where test files will be created
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
defer os.RemoveAll(tempBaseDir)
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
ts, _ := createTestServer(t)
defer ts.Close()
repo, err := client.NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, oldPassphraseRetriever)
assert.NoError(t, err, "error creating repo: %s", err)
rootKeyID, err := repo.KeyStoreManager.GenRootKey(data.ECDSAKey.String())
assert.NoError(t, err, "error generating root key: %s", err)
rootCryptoService, err := repo.KeyStoreManager.GetRootCryptoService(rootKeyID)
assert.NoError(t, err, "error retrieving root key: %s", err)
err = repo.Initialize(rootCryptoService)
assert.NoError(t, err, "error creating repository: %s", err)
tempKeyFile, err := ioutil.TempFile("", "notary-test-export-")
tempKeyFilePath := tempKeyFile.Name()
defer os.Remove(tempKeyFilePath)
err = repo.KeyStoreManager.ExportRootKeyReencrypt(tempKeyFile, rootKeyID, newPassphraseRetriever)
assert.NoError(t, err)
tempKeyFile.Close()
// Create new repo to test import
tempBaseDir2, err := ioutil.TempDir("", "notary-test-")
defer os.RemoveAll(tempBaseDir2)
assert.NoError(t, err, "failed to create a temporary directory: %s", err)
repo2, err := client.NewNotaryRepository(tempBaseDir2, gun, ts.URL, http.DefaultTransport, newPassphraseRetriever)
assert.NoError(t, err, "error creating repo: %s", err)
rootKeyID2, err := repo2.KeyStoreManager.GenRootKey(data.ECDSAKey.String())
assert.NoError(t, err, "error generating root key: %s", err)
rootCryptoService2, err := repo2.KeyStoreManager.GetRootCryptoService(rootKeyID2)
assert.NoError(t, err, "error retrieving root key: %s", err)
err = repo2.Initialize(rootCryptoService2)
assert.NoError(t, err, "error creating repository: %s", err)
keyReader, err := os.Open(tempKeyFilePath)
assert.NoError(t, err, "could not open key file")
err = repo2.KeyStoreManager.ImportRootKey(keyReader, rootKeyID)
assert.NoError(t, err)
keyReader.Close()
// Look for repo's root key in repo2
// There should be a file named after the key ID of the root key we
// imported.
rootKeyFilename := rootKeyID + "_root.key"
_, err = os.Stat(filepath.Join(tempBaseDir2, "private", "root_keys", rootKeyFilename))
assert.NoError(t, err, "missing root key")
// Should be able to unlock the root key with the new password
key, alias, err := repo2.KeyStoreManager.RootKeyStore().GetKey(rootKeyID)
assert.NoError(t, err, "could not unlock root key")
assert.Equal(t, "root", alias)
assert.Equal(t, rootKeyID, key.ID())
} }