mirror of
https://github.com/docker/docs.git
synced 2026-04-02 09:18:57 +07:00
Add ImportKeysZip and test coverage
This function reads a zip file and populates the keystores with the keys in the zip file. Root keys are left encrypted, and non-root keys are decrypted before being added to the keystore. The unit test first exports a repo's keys to a zip file, then imports it into another repo. It checks that all the correct keys exist in the new repo after the import operation. Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
This commit is contained in:
@@ -11,7 +11,9 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/notary/trustmanager"
|
||||
"github.com/endophage/gotuf/data"
|
||||
)
|
||||
|
||||
func moveKeysWithNewPassphrase(oldKeyStore, newKeyStore *trustmanager.KeyFileStore, outputPassphrase string) error {
|
||||
@@ -138,10 +140,75 @@ func (km *KeyStoreManager) ExportAllKeys(dest io.Writer, outputPassphrase string
|
||||
return nil
|
||||
}
|
||||
|
||||
// ImportZip imports keys from a zip file provided as an io.Reader. The keys
|
||||
// in the root_keys directory are left encrypted, but the other keys are
|
||||
// ImportKeysZip imports keys from a zip file provided as an io.ReaderAt. The
|
||||
// keys in the root_keys directory are left encrypted, but the other keys are
|
||||
// decrypted with the specified passphrase.
|
||||
func (km *KeyStoreManager) ImportZip(zip io.Reader, passphrase string) error {
|
||||
// TODO(aaronl)
|
||||
func (km *KeyStoreManager) ImportKeysZip(zipReader zip.Reader, passphrase string) error {
|
||||
// Temporarily store the keys in maps, so we can bail early if there's
|
||||
// an error (for example, wrong passphrase), without leaving the key
|
||||
// store in an inconsistent state
|
||||
newRootKeys := make(map[string][]byte)
|
||||
newNonRootKeys := make(map[string]*data.PrivateKey)
|
||||
|
||||
// Iterate through the files in the archive. Don't add the keys
|
||||
for _, f := range zipReader.File {
|
||||
fNameTrimmed := strings.TrimSuffix(f.Name, filepath.Ext(f.Name))
|
||||
// Note that using / as a separator is okay here - the zip
|
||||
// package guarantees that the separator will be /
|
||||
keysPrefix := privDir + "/"
|
||||
|
||||
if !strings.HasPrefix(fNameTrimmed, keysPrefix) {
|
||||
// This path inside the zip archive doesn't start with
|
||||
// "private". That's unexpected, because all keys
|
||||
// should be in that subdirectory. To avoid adding a
|
||||
// file to the filestore that we won't be able to use,
|
||||
// skip this file in the import.
|
||||
logrus.Warnf("skipping import of key with a path that doesn't begin with %s: %s", keysPrefix, f.Name)
|
||||
continue
|
||||
}
|
||||
fNameTrimmed = strings.TrimPrefix(fNameTrimmed, keysPrefix)
|
||||
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pemBytes, err := ioutil.ReadAll(rc)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Is this in the root_keys directory?
|
||||
// Note that using / as a separator is okay here - the zip
|
||||
// package guarantees that the separator will be /
|
||||
rootKeysPrefix := rootKeysSubdir + "/"
|
||||
if strings.HasPrefix(fNameTrimmed, rootKeysPrefix) {
|
||||
// Root keys are preserved without decrypting
|
||||
keyName := strings.TrimPrefix(fNameTrimmed, rootKeysPrefix)
|
||||
newRootKeys[keyName] = pemBytes
|
||||
} else {
|
||||
// Non-root keys need to be decrypted
|
||||
key, err := trustmanager.ParsePEMPrivateKey(pemBytes, passphrase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newNonRootKeys[fNameTrimmed] = key
|
||||
}
|
||||
|
||||
rc.Close()
|
||||
}
|
||||
|
||||
for keyName, pemBytes := range newRootKeys {
|
||||
if err := km.rootKeyStore.Add(keyName, pemBytes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for keyName, privKey := range newNonRootKeys {
|
||||
if err := km.nonRootKeyStore.AddKey(keyName, privKey); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -35,8 +35,11 @@ func createTestServer(t *testing.T) (*httptest.Server, *http.ServeMux) {
|
||||
return ts, mux
|
||||
}
|
||||
|
||||
func TestExportKeys(t *testing.T) {
|
||||
func TestImportExportZip(t *testing.T) {
|
||||
gun := "docker.com/notary"
|
||||
oldPassphrase := "oldPassphrase"
|
||||
exportPassphrase := "exportPassphrase"
|
||||
|
||||
// Temporary directory where test files will be created
|
||||
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
|
||||
defer os.RemoveAll(tempBaseDir)
|
||||
@@ -49,10 +52,10 @@ func TestExportKeys(t *testing.T) {
|
||||
repo, err := client.NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport)
|
||||
assert.NoError(t, err, "error creating repo: %s", err)
|
||||
|
||||
rootKeyID, err := repo.KeyStoreManager.GenRootKey(data.ECDSAKey.String(), "oldPassphrase")
|
||||
rootKeyID, err := repo.KeyStoreManager.GenRootKey(data.ECDSAKey.String(), oldPassphrase)
|
||||
assert.NoError(t, err, "error generating root key: %s", err)
|
||||
|
||||
rootCryptoService, err := repo.KeyStoreManager.GetRootCryptoService(rootKeyID, "oldPassphrase")
|
||||
rootCryptoService, err := repo.KeyStoreManager.GetRootCryptoService(rootKeyID, oldPassphrase)
|
||||
assert.NoError(t, err, "error retrieving root key: %s", err)
|
||||
|
||||
err = repo.Initialize(rootCryptoService)
|
||||
@@ -62,14 +65,13 @@ func TestExportKeys(t *testing.T) {
|
||||
tempZipFilePath := tempZipFile.Name()
|
||||
defer os.Remove(tempZipFilePath)
|
||||
|
||||
err = repo.KeyStoreManager.ExportAllKeys(tempZipFile, "exportPassphrase")
|
||||
err = repo.KeyStoreManager.ExportAllKeys(tempZipFile, exportPassphrase)
|
||||
tempZipFile.Close()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check the contents of the zip file
|
||||
zipReader, err := zip.OpenReader(tempZipFilePath)
|
||||
assert.NoError(t, err, "could not open zip file")
|
||||
defer zipReader.Close()
|
||||
|
||||
// Map of files to expect in the zip file, with the passphrases
|
||||
passphraseByFile := make(map[string]string)
|
||||
@@ -79,13 +81,13 @@ func TestExportKeys(t *testing.T) {
|
||||
privKeyList := repo.KeyStoreManager.NonRootKeyStore().ListFiles(false)
|
||||
for _, privKeyName := range privKeyList {
|
||||
relName := strings.TrimPrefix(privKeyName, tempBaseDir+string(filepath.Separator))
|
||||
passphraseByFile[relName] = "exportPassphrase"
|
||||
passphraseByFile[relName] = exportPassphrase
|
||||
}
|
||||
|
||||
// Add root key to the map. This will use the old passphrase because it
|
||||
// won't be reencrypted.
|
||||
relRootKey := filepath.Join("private", "root_keys", rootCryptoService.ID()+".key")
|
||||
passphraseByFile[relRootKey] = "oldPassphrase"
|
||||
passphraseByFile[relRootKey] = oldPassphrase
|
||||
|
||||
// Iterate through the files in the archive, checking that the files
|
||||
// exist and are encrypted with the expected passphrase.
|
||||
@@ -109,8 +111,65 @@ func TestExportKeys(t *testing.T) {
|
||||
rc.Close()
|
||||
}
|
||||
|
||||
zipReader.Close()
|
||||
|
||||
// Are there any keys that didn't make it to the zip?
|
||||
for fileNotFound, _ := range passphraseByFile {
|
||||
for fileNotFound := range passphraseByFile {
|
||||
t.Fatalf("%s not found in zip", fileNotFound)
|
||||
}
|
||||
|
||||
// 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)
|
||||
assert.NoError(t, err, "error creating repo: %s", err)
|
||||
|
||||
rootKeyID2, err := repo2.KeyStoreManager.GenRootKey(data.ECDSAKey.String(), "oldPassphrase")
|
||||
assert.NoError(t, err, "error generating root key: %s", err)
|
||||
|
||||
rootCryptoService2, err := repo2.KeyStoreManager.GetRootCryptoService(rootKeyID2, "oldPassphrase")
|
||||
assert.NoError(t, err, "error retrieving root key: %s", err)
|
||||
|
||||
err = repo2.Initialize(rootCryptoService2)
|
||||
assert.NoError(t, err, "error creating repository: %s", err)
|
||||
|
||||
// Reopen the zip file for importing
|
||||
zipReader, err = zip.OpenReader(tempZipFilePath)
|
||||
assert.NoError(t, err, "could not open zip file")
|
||||
|
||||
// First try with an incorrect passphrase
|
||||
err = repo2.KeyStoreManager.ImportKeysZip(zipReader.Reader, "wrongpassphrase")
|
||||
// Don't use EqualError here because occasionally decrypting with the
|
||||
// wrong passphrase returns a parse error
|
||||
assert.Error(t, err)
|
||||
zipReader.Close()
|
||||
|
||||
// Reopen the zip file for importing
|
||||
zipReader, err = zip.OpenReader(tempZipFilePath)
|
||||
assert.NoError(t, err, "could not open zip file")
|
||||
|
||||
// Now try with a valid passphrase. This time it should succeed.
|
||||
err = repo2.KeyStoreManager.ImportKeysZip(zipReader.Reader, exportPassphrase)
|
||||
assert.NoError(t, err)
|
||||
zipReader.Close()
|
||||
|
||||
// Look for repo's keys in repo2
|
||||
|
||||
// Look for keys in private. The filenames should match the key IDs
|
||||
// in the repo's private key store.
|
||||
for _, privKeyName := range privKeyList {
|
||||
privKeyRel := strings.TrimPrefix(privKeyName, tempBaseDir)
|
||||
_, err := os.Stat(filepath.Join(tempBaseDir2, privKeyRel))
|
||||
assert.NoError(t, err, "missing private key: %s", privKeyName)
|
||||
}
|
||||
|
||||
// Look for keys in root_keys
|
||||
// There should be a file named after the key ID of the root key we
|
||||
// passed in.
|
||||
rootKeyFilename := rootCryptoService.ID() + ".key"
|
||||
_, err = os.Stat(filepath.Join(tempBaseDir2, "private", "root_keys", rootKeyFilename))
|
||||
assert.NoError(t, err, "missing root key")
|
||||
}
|
||||
Reference in New Issue
Block a user