Skip to content

Commit

Permalink
re-implement import/export
Browse files Browse the repository at this point in the history
Signed-off-by: David Lawrence <[email protected]> (github: endophage)
  • Loading branch information
David Lawrence committed Jul 18, 2016
1 parent 635b90e commit dff7044
Show file tree
Hide file tree
Showing 17 changed files with 1,194 additions and 59 deletions.
16 changes: 8 additions & 8 deletions client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3341,19 +3341,19 @@ func TestDeleteRemoteRepo(t *testing.T) {
// Try connecting to the remote store directly and make sure that no metadata exists for this gun
remoteStore, err := getRemoteStore(repo.baseURL, repo.gun, repo.roundTrip)
require.NoError(t, err)
meta, err := remoteStore.GetMeta(data.CanonicalRootRole, store.NoSizeLimit)
meta, err := remoteStore.GetSized(data.CanonicalRootRole, store.NoSizeLimit)
require.Error(t, err)
require.IsType(t, store.ErrMetaNotFound{}, err)
require.Nil(t, meta)
meta, err = remoteStore.GetMeta(data.CanonicalTargetsRole, store.NoSizeLimit)
meta, err = remoteStore.GetSized(data.CanonicalTargetsRole, store.NoSizeLimit)
require.Error(t, err)
require.IsType(t, store.ErrMetaNotFound{}, err)
require.Nil(t, meta)
meta, err = remoteStore.GetMeta(data.CanonicalSnapshotRole, store.NoSizeLimit)
meta, err = remoteStore.GetSized(data.CanonicalSnapshotRole, store.NoSizeLimit)
require.Error(t, err)
require.IsType(t, store.ErrMetaNotFound{}, err)
require.Nil(t, meta)
meta, err = remoteStore.GetMeta(data.CanonicalTimestampRole, store.NoSizeLimit)
meta, err = remoteStore.GetSized(data.CanonicalTimestampRole, store.NoSizeLimit)
require.Error(t, err)
require.IsType(t, store.ErrMetaNotFound{}, err)
require.Nil(t, meta)
Expand All @@ -3364,16 +3364,16 @@ func TestDeleteRemoteRepo(t *testing.T) {
requireRepoHasExpectedMetadata(t, longLivingRepo, data.CanonicalSnapshotRole, true)
remoteStore, err = getRemoteStore(longLivingRepo.baseURL, longLivingRepo.gun, longLivingRepo.roundTrip)
require.NoError(t, err)
meta, err = remoteStore.GetMeta(data.CanonicalRootRole, store.NoSizeLimit)
meta, err = remoteStore.GetSized(data.CanonicalRootRole, store.NoSizeLimit)
require.NoError(t, err)
require.NotNil(t, meta)
meta, err = remoteStore.GetMeta(data.CanonicalTargetsRole, store.NoSizeLimit)
meta, err = remoteStore.GetSized(data.CanonicalTargetsRole, store.NoSizeLimit)
require.NoError(t, err)
require.NotNil(t, meta)
meta, err = remoteStore.GetMeta(data.CanonicalSnapshotRole, store.NoSizeLimit)
meta, err = remoteStore.GetSized(data.CanonicalSnapshotRole, store.NoSizeLimit)
require.NoError(t, err)
require.NotNil(t, meta)
meta, err = remoteStore.GetMeta(data.CanonicalTimestampRole, store.NoSizeLimit)
meta, err = remoteStore.GetSized(data.CanonicalTimestampRole, store.NoSizeLimit)
require.NoError(t, err)
require.NotNil(t, meta)

Expand Down
124 changes: 121 additions & 3 deletions cmd/notary/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ import (

notaryclient "github.com/docker/notary/client"
"github.com/docker/notary/cryptoservice"
store "github.com/docker/notary/storage"
"github.com/docker/notary/trustmanager"
"github.com/docker/notary/utils"

"github.com/docker/notary"
"github.com/docker/notary/tuf/data"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"os"
)

var cmdKeyTemplate = usageTemplate{
Expand Down Expand Up @@ -53,6 +56,18 @@ var cmdKeyPasswdTemplate = usageTemplate{
Long: "Changes the passphrase for the key with the given keyID. Will require validation of the old passphrase.",
}

var cmdKeyImportTemplate = usageTemplate{
Use: "import pemfile [ pemfile ... ]",
Short: "Imports all keys from all provided .pem files",
Long: "Imports all keys from all provided .pem files by reading each PEM block from the file and writing that block to a unique object in the local keystore. A Yubikey will be the prefferred import location for root keys if present.",
}

var cmdKeyExportTemplate = usageTemplate{
Use: "export",
Short: "Exports all keys from all local keystores. Can be filtered using the --key and --gun flags.",
Long: "Exports all keys from all local keystores. Which keys are exported can be restricted by using the --key or --gun flags. By default the result is sent to stdout, it can be directed to a file with the -o flag. Keys stored in a Yubikey cannot be exported.",
}

type keyCommander struct {
// these need to be set
configGetter func() (*viper.Viper, error)
Expand All @@ -63,6 +78,10 @@ type keyCommander struct {
rotateKeyServerManaged bool

input io.Reader

exportGUNs []string
exportKeyIDs []string
outFile string
}

func (k *keyCommander) GetCommand() *cobra.Command {
Expand All @@ -78,6 +97,28 @@ func (k *keyCommander) GetCommand() *cobra.Command {
"Required for timestamp role, optional for snapshot role")
cmd.AddCommand(cmdRotateKey)

cmd.AddCommand(cmdKeyImportTemplate.ToCommand(k.importKeys))
cmdExport := cmdKeyExportTemplate.ToCommand(k.exportKeys)
cmdExport.Flags().StringSliceVar(
&k.exportGUNs,
"gun",
nil,
"GUNs for which to export keys",
)
cmdExport.Flags().StringSliceVar(
&k.exportKeyIDs,
"key",
nil,
"Key IDs to export",
)
cmdExport.Flags().StringVarP(
&k.outFile,
"output",
"o",
"",
"Filepath to write export output to",
)
cmd.AddCommand(cmdExport)
return cmd
}

Expand Down Expand Up @@ -345,14 +386,91 @@ func (k *keyCommander) keyPassphraseChange(cmd *cobra.Command, args []string) er
if err != nil {
return err
}
cmd.Println("")
cmd.Printf("Successfully updated passphrase for key ID: %s", keyID)
cmd.Println("")
cmd.Printf("\nSuccessfully updated passphrase for key ID: %s\n", keyID)
return nil
}

func (k *keyCommander) importKeys(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
cmd.Usage()
return fmt.Errorf("must specify at least one input file to import keys from")
}
config, err := k.configGetter()
if err != nil {
return err
}

directory := config.GetString("trust_dir")
importers, err := getImporters(directory, k.getRetriever())
if err != nil {
return err
}
for _, file := range args {
from, err := os.OpenFile(file, os.O_RDONLY, notary.PrivKeyPerms)
defer from.Close()

if err = utils.ImportKeys(from, importers); err != nil {
return err
}
}
return nil
}

func (k *keyCommander) exportKeys(cmd *cobra.Command, args []string) error {
var (
out io.Writer
err error
)
if len(args) > 0 {
cmd.Usage()
return fmt.Errorf("export does not take any positional arguments")
}
config, err := k.configGetter()
if err != nil {
return err
}

if k.outFile == "" {
out = cmd.Out()
} else {
f, err := os.OpenFile(k.outFile, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, notary.PrivKeyPerms)
if err != nil {
return err
}
defer f.Close()
out = f
}

directory := config.GetString("trust_dir")
fileStore, err := store.NewPrivateKeyFileStorage(directory, notary.KeyExtension)
if err != nil {
return err
}
if len(k.exportGUNs) > 0 {
if len(k.exportKeyIDs) > 0 {
return fmt.Errorf("Only the --gun or --key flag may be provided, not a mix of the two flags")
}
for _, gun := range k.exportGUNs {
gunPath := filepath.Join(notary.NonRootKeysSubdir, gun)
return utils.ExportKeysByGUN(out, fileStore, gunPath)
}
} else if len(k.exportKeyIDs) > 0 {
return utils.ExportKeysByID(out, fileStore, k.exportKeyIDs)
}
// export everything
keys := fileStore.ListFiles()
for _, k := range keys {
err := utils.ExportKeys(out, fileStore, k)
if err != nil {
return err
}
}
return nil
}

func (k *keyCommander) getKeyStores(
config *viper.Viper, withHardware, hardwareBackup bool) ([]trustmanager.KeyStore, error) {

retriever := k.getRetriever()

directory := config.GetString("trust_dir")
Expand Down
10 changes: 10 additions & 0 deletions cmd/notary/keys_nonpkcs11.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,19 @@ import (
"errors"

"github.com/docker/notary"
store "github.com/docker/notary/storage"
"github.com/docker/notary/trustmanager"
"github.com/docker/notary/utils"
)

func getYubiStore(fileKeyStore trustmanager.KeyStore, ret notary.PassRetriever) (trustmanager.KeyStore, error) {
return nil, errors.New("Not built with hardware support")
}

func getImporters(baseDir string, _ notary.PassRetriever) ([]utils.Importer, error) {
fileStore, err := store.NewPrivateKeyFileStorage(baseDir, notary.KeyExtension)
if err != nil {
return nil, err
}
return []utils.Importer{fileStore}, nil
}
151 changes: 151 additions & 0 deletions cmd/notary/keys_nonpkcs11_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
//+build !pkcs11

package main

import (
"encoding/pem"
"github.com/docker/notary"
"github.com/docker/notary/cryptoservice"
"github.com/docker/notary/passphrase"
store "github.com/docker/notary/storage"
"github.com/docker/notary/trustmanager"
"github.com/docker/notary/tuf/data"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
"io/ioutil"
"os"
"path/filepath"
"testing"
)

func TestImportKeysNoYubikey(t *testing.T) {
setUp(t)
tempBaseDir, err := ioutil.TempDir("/tmp", "notary-test-")
require.NoError(t, err)
defer os.RemoveAll(tempBaseDir)
input, err := ioutil.TempFile("/tmp", "notary-test-import-")
require.NoError(t, err)
defer os.RemoveAll(input.Name())
k := &keyCommander{
configGetter: func() (*viper.Viper, error) {
v := viper.New()
v.SetDefault("trust_dir", tempBaseDir)
return v, nil
},
getRetriever: func() notary.PassRetriever { return passphrase.ConstantRetriever("pass") },
}

memStore := store.NewMemoryStore(nil)
ks := trustmanager.NewGenericKeyStore(memStore, k.getRetriever())
cs := cryptoservice.NewCryptoService(ks)

pubK, err := cs.Create(data.CanonicalRootRole, "ankh", data.ECDSAKey)
require.NoError(t, err)
bytes, err := memStore.Get(notary.RootKeysSubdir + "/" + pubK.ID())
require.NoError(t, err)
b, _ := pem.Decode(bytes)
b.Headers["path"] = "ankh"

pubK, err = cs.Create(data.CanonicalTargetsRole, "morpork", data.ECDSAKey)
require.NoError(t, err)
bytes, err = memStore.Get(notary.NonRootKeysSubdir + "/morpork/" + pubK.ID())
require.NoError(t, err)
c, _ := pem.Decode(bytes)
c.Headers["path"] = "morpork"

bBytes := pem.EncodeToMemory(b)
cBytes := pem.EncodeToMemory(c)
input.Write(bBytes)
input.Write(cBytes)

file := input.Name()
err = input.Close() // close so import can open
require.NoError(t, err)

err = k.importKeys(&cobra.Command{}, []string{file})
require.NoError(t, err)

fileStore, err := store.NewPrivateKeyFileStorage(tempBaseDir, notary.KeyExtension)
bResult, err := fileStore.Get("ankh")
require.NoError(t, err)
cResult, err := fileStore.Get("morpork")
require.NoError(t, err)

block, rest := pem.Decode(bResult)
require.Equal(t, b.Bytes, block.Bytes)
require.Len(t, rest, 0)

block, rest = pem.Decode(cResult)
require.Equal(t, c.Bytes, block.Bytes)
require.Len(t, rest, 0)
}

func TestExportImportKeysNoYubikey(t *testing.T) {
setUp(t)
exportTempDir, err := ioutil.TempDir("/tmp", "notary-test-")
require.NoError(t, err)
defer os.RemoveAll(exportTempDir)
tempfile, err := ioutil.TempFile("/tmp", "notary-test-import-")
require.NoError(t, err)
tempfile.Close()
defer os.RemoveAll(tempfile.Name())
exportCommander := &keyCommander{
configGetter: func() (*viper.Viper, error) {
v := viper.New()
v.SetDefault("trust_dir", exportTempDir)
return v, nil
},
getRetriever: func() notary.PassRetriever { return passphrase.ConstantRetriever("pass") },
}
exportCommander.outFile = tempfile.Name()

exportStore, err := store.NewPrivateKeyFileStorage(exportTempDir, notary.KeyExtension)
ks := trustmanager.NewGenericKeyStore(exportStore, exportCommander.getRetriever())
cs := cryptoservice.NewCryptoService(ks)

pubK, err := cs.Create(data.CanonicalRootRole, "ankh", data.ECDSAKey)
require.NoError(t, err)
bID := pubK.ID()
bOrigBytes, err := exportStore.Get(filepath.Join(notary.RootKeysSubdir, bID))
require.NoError(t, err)
bOrig, _ := pem.Decode(bOrigBytes)

pubK, err = cs.Create(data.CanonicalTargetsRole, "morpork", data.ECDSAKey)
require.NoError(t, err)
cID := pubK.ID()
cOrigBytes, err := exportStore.Get(filepath.Join(notary.NonRootKeysSubdir, "morpork", cID))
require.NoError(t, err)
cOrig, _ := pem.Decode(cOrigBytes)

exportCommander.exportKeys(&cobra.Command{}, nil)

importTempDir, err := ioutil.TempDir("/tmp", "notary-test-")
require.NoError(t, err)
defer os.RemoveAll(importTempDir)
importCommander := &keyCommander{
configGetter: func() (*viper.Viper, error) {
v := viper.New()
v.SetDefault("trust_dir", importTempDir)
return v, nil
},
getRetriever: func() notary.PassRetriever { return passphrase.ConstantRetriever("pass") },
}

err = importCommander.importKeys(&cobra.Command{}, []string{tempfile.Name()})
require.NoError(t, err)

importStore, err := store.NewPrivateKeyFileStorage(importTempDir, notary.KeyExtension)
bResult, err := importStore.Get(filepath.Join(notary.RootKeysSubdir, bID))
require.NoError(t, err)
cResult, err := importStore.Get(filepath.Join(notary.NonRootKeysSubdir, "morpork", cID))
require.NoError(t, err)

block, rest := pem.Decode(bResult)
require.Equal(t, bOrig.Bytes, block.Bytes)
require.Len(t, rest, 0)

block, rest = pem.Decode(cResult)
require.Equal(t, cOrig.Bytes, block.Bytes)
require.Len(t, rest, 0)
}
Loading

0 comments on commit dff7044

Please sign in to comment.