mirror of https://github.com/docker/docs.git
Merge pull request #147 from docker/import-export-cli
Import export cli
This commit is contained in:
commit
e0e574f9ff
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue