mirror of https://github.com/docker/docs.git
Merge pull request #30 from docker/client-integration-tests
Adding integration tests for notary client. Signed-off-by: David Lawrence <david.lawrence@docker.com> Signed-off-by: Ying Li <cyli@users.noreply.github.com> (github: endophage)
This commit is contained in:
commit
4698ad69f2
|
|
@ -2,7 +2,6 @@ package main
|
|||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
|
@ -92,14 +91,14 @@ func certRemove(cmd *cobra.Command, args []string) {
|
|||
}
|
||||
|
||||
// List all the keys about to be removed
|
||||
fmt.Printf("The following certificates will be removed:\n\n")
|
||||
cmd.Printf("The following certificates will be removed:\n\n")
|
||||
for _, cert := range certsToRemove {
|
||||
// This error can't occur because we're getting certs off of an
|
||||
// x509 store that indexes by ID.
|
||||
certID, _ := trustmanager.FingerprintCert(cert)
|
||||
fmt.Printf("%s - %s\n", cert.Subject.CommonName, certID)
|
||||
cmd.Printf("%s - %s\n", cert.Subject.CommonName, certID)
|
||||
}
|
||||
fmt.Println("\nAre you sure you want to remove these certificates? (yes/no)")
|
||||
cmd.Println("\nAre you sure you want to remove these certificates? (yes/no)")
|
||||
|
||||
// Ask for confirmation before removing certificates, unless -y is provided
|
||||
if !certRemoveYes {
|
||||
|
|
@ -136,20 +135,20 @@ func certList(cmd *cobra.Command, args []string) {
|
|||
fatalf("failed to create a new truststore manager with directory: %s", trustDir)
|
||||
}
|
||||
|
||||
fmt.Println("")
|
||||
fmt.Println("# Trusted Certificates:")
|
||||
cmd.Println("")
|
||||
cmd.Println("# Trusted Certificates:")
|
||||
trustedCerts := keyStoreManager.TrustedCertificateStore().GetCertificates()
|
||||
for _, c := range trustedCerts {
|
||||
printCert(c)
|
||||
printCert(cmd, c)
|
||||
}
|
||||
}
|
||||
|
||||
func printCert(cert *x509.Certificate) {
|
||||
func printCert(cmd *cobra.Command, cert *x509.Certificate) {
|
||||
timeDifference := cert.NotAfter.Sub(time.Now())
|
||||
certID, err := trustmanager.FingerprintCert(cert)
|
||||
if err != nil {
|
||||
fatalf("could not fingerprint certificate: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%s %s (expires in: %v days)\n", cert.Subject.CommonName, certID, math.Floor(timeDifference.Hours()/24))
|
||||
cmd.Printf("%s %s (expires in: %v days)\n", cert.Subject.CommonName, certID, math.Floor(timeDifference.Hours()/24))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
// +build !pkcs11
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/notary/passphrase"
|
||||
)
|
||||
|
||||
func rootOnHardware() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Per-test set up that returns a cleanup function. This set up changes the
|
||||
// passphrase retriever to always produce a constant passphrase
|
||||
func setUp(t *testing.T) func() {
|
||||
oldRetriever := retriever
|
||||
|
||||
var fake = func(k, a string, c bool, n int) (string, bool, error) {
|
||||
return testPassphrase, false, nil
|
||||
}
|
||||
|
||||
retriever = fake
|
||||
getRetriever = func() passphrase.Retriever { return fake }
|
||||
|
||||
return func() {
|
||||
retriever = oldRetriever
|
||||
getRetriever = getPassphraseRetriever
|
||||
}
|
||||
}
|
||||
|
||||
// no-op
|
||||
func verifyRootKeyOnHardware(t *testing.T, rootKeyID string) {}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
// +build pkcs11
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/notary/passphrase"
|
||||
"github.com/docker/notary/signer/api"
|
||||
"github.com/docker/notary/tuf/data"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var rootOnHardware = api.YubikeyAccessible
|
||||
|
||||
// Per-test set up that returns a cleanup function. This set up:
|
||||
// - changes the passphrase retriever to always produce a constant passphrase
|
||||
// - disables touch on yubikeys
|
||||
// - deletes all keys on the yubikey
|
||||
func setUp(t *testing.T) func() {
|
||||
oldRetriever := retriever
|
||||
|
||||
var fake = func(k, a string, c bool, n int) (string, bool, error) {
|
||||
if k == "Yubikey" {
|
||||
return oldRetriever(k, a, c, n)
|
||||
}
|
||||
return testPassphrase, false, nil
|
||||
}
|
||||
|
||||
retriever = fake
|
||||
getRetriever = func() passphrase.Retriever { return fake }
|
||||
api.SetYubikeyKeyMode(api.KeymodeNone)
|
||||
|
||||
// //we're just removing keys here, so nil is fine
|
||||
s, err := api.NewYubiKeyStore(nil, retriever)
|
||||
assert.NoError(t, err)
|
||||
for k := range s.ListKeys() {
|
||||
err := s.RemoveKey(k)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
return func() {
|
||||
retriever = oldRetriever
|
||||
getRetriever = getPassphraseRetriever
|
||||
api.SetYubikeyKeyMode(api.KeymodeTouch | api.KeymodePinOnce)
|
||||
}
|
||||
}
|
||||
|
||||
// ensures that the root is actually on the yubikey - this makes sure the
|
||||
// commands are hooked up to interact with the yubikey, rather than right files
|
||||
// on disk
|
||||
func verifyRootKeyOnHardware(t *testing.T, rootKeyID string) {
|
||||
// do not bother verifying if there is no yubikey available
|
||||
if api.YubikeyAccessible() {
|
||||
// //we're just getting keys here, so nil is fine
|
||||
s, err := api.NewYubiKeyStore(nil, retriever)
|
||||
assert.NoError(t, err)
|
||||
privKey, role, err := s.GetKey(rootKeyID)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, privKey)
|
||||
assert.Equal(t, data.CanonicalRootRole, role)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,513 @@
|
|||
// Actually start up a notary server and run through basic TUF and key
|
||||
// interactions via the client.
|
||||
|
||||
// Note - if using Yubikey, retrieving pins/touch doesn't seem to work right
|
||||
// when running in the midst of all tests.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"io/ioutil"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
ctxu "github.com/docker/distribution/context"
|
||||
"github.com/docker/notary/cryptoservice"
|
||||
"github.com/docker/notary/server"
|
||||
"github.com/docker/notary/server/storage"
|
||||
"github.com/docker/notary/trustmanager"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var cmd = &cobra.Command{}
|
||||
var testPassphrase = "passphrase"
|
||||
|
||||
// run a command and return the output as a string
|
||||
func runCommand(t *testing.T, tempDir string, args ...string) (string, error) {
|
||||
b := new(bytes.Buffer)
|
||||
cmd.SetArgs(append([]string{"-c", "/tmp/ignore.json", "-d", tempDir}, args...))
|
||||
cmd.SetOutput(b)
|
||||
t.Logf("Running `notary %s`", strings.Join(args, " "))
|
||||
|
||||
retErr := cmd.Execute()
|
||||
output, err := ioutil.ReadAll(b)
|
||||
assert.NoError(t, err)
|
||||
return string(output), retErr
|
||||
}
|
||||
|
||||
// makes a testing notary-server
|
||||
func setupServer() *httptest.Server {
|
||||
// Set up 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")
|
||||
|
||||
// Eat the logs instead of spewing them out
|
||||
var b bytes.Buffer
|
||||
l := logrus.New()
|
||||
l.Out = &b
|
||||
ctx = ctxu.WithLogger(ctx, logrus.NewEntry(l))
|
||||
|
||||
cryptoService := cryptoservice.NewCryptoService(
|
||||
"", trustmanager.NewKeyMemoryStore(retriever))
|
||||
return httptest.NewServer(server.RootHandler(nil, ctx, cryptoService))
|
||||
}
|
||||
|
||||
// Initializes a repo, adds a target, publishes the target, lists the target,
|
||||
// verifies the target, and then removes the target.
|
||||
func TestClientTufInteraction(t *testing.T) {
|
||||
// -- setup --
|
||||
cleanup := setUp(t)
|
||||
defer cleanup()
|
||||
|
||||
tempDir, err := ioutil.TempDir("/tmp", "repo")
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
server := setupServer()
|
||||
defer server.Close()
|
||||
|
||||
tempFile, err := ioutil.TempFile("/tmp", "targetfile")
|
||||
assert.NoError(t, err)
|
||||
tempFile.Close()
|
||||
defer os.Remove(tempFile.Name())
|
||||
|
||||
var (
|
||||
output string
|
||||
target = "sdgkadga"
|
||||
)
|
||||
// -- tests --
|
||||
|
||||
// init repo
|
||||
_, err = runCommand(t, tempDir, "-s", server.URL, "init", "gun")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// add a target
|
||||
_, err = runCommand(t, tempDir, "add", "gun", target, tempFile.Name())
|
||||
assert.NoError(t, err)
|
||||
|
||||
// check status - see target
|
||||
output, err = runCommand(t, tempDir, "status", "gun")
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, strings.Contains(output, target))
|
||||
|
||||
// publish repo
|
||||
_, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// check status - no targets
|
||||
output, err = runCommand(t, tempDir, "status", "gun")
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, strings.Contains(string(output), target))
|
||||
|
||||
// list repo - see target
|
||||
output, err = runCommand(t, tempDir, "-s", server.URL, "list", "gun")
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, strings.Contains(string(output), target))
|
||||
|
||||
// verify repo - empty file
|
||||
output, err = runCommand(t, tempDir, "verify", "gun", target)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// remove target
|
||||
_, err = runCommand(t, tempDir, "remove", "gun", target)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// publish repo
|
||||
_, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// list repo - don't see target
|
||||
output, err = runCommand(t, tempDir, "-s", server.URL, "list", "gun")
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, strings.Contains(string(output), target))
|
||||
}
|
||||
|
||||
// Splits a string into lines, and returns any lines that are not empty (
|
||||
// striped of whitespace)
|
||||
func splitLines(chunk string) []string {
|
||||
splitted := strings.Split(strings.TrimSpace(chunk), "\n")
|
||||
var results []string
|
||||
|
||||
for _, line := range splitted {
|
||||
line := strings.TrimSpace(line)
|
||||
if line != "" {
|
||||
results = append(results, line)
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// List keys, parses the output, and returns the keys as an array of root key
|
||||
// IDs and an array of signing key IDs
|
||||
func GetKeys(t *testing.T, tempDir string) ([]string, []string) {
|
||||
output, err := runCommand(t, tempDir, "key", "list")
|
||||
assert.NoError(t, err)
|
||||
|
||||
parts := strings.Split(output, "# Signing keys:")
|
||||
assert.Len(t, parts, 2)
|
||||
|
||||
fixed := make([][]string, 2)
|
||||
for i, part := range parts {
|
||||
fixed[i] = splitLines(
|
||||
strings.TrimPrefix(strings.TrimSpace(part), "# Root keys:"))
|
||||
sort.Strings(fixed[i])
|
||||
}
|
||||
|
||||
return fixed[0], fixed[1]
|
||||
}
|
||||
|
||||
// List keys, parses the output, and asserts something about the number of root
|
||||
// keys and number of signing keys, as well as returning them.
|
||||
func assertNumKeys(t *testing.T, tempDir string, numRoot, numSigning int) (
|
||||
[]string, []string) {
|
||||
|
||||
root, signing := GetKeys(t, tempDir)
|
||||
assert.Len(t, root, numRoot)
|
||||
assert.Len(t, signing, numSigning)
|
||||
for _, rootKeyID := range root {
|
||||
// it should always be present on disk
|
||||
_, err := os.Stat(filepath.Join(
|
||||
tempDir, "private", "root_keys", rootKeyID+"_root.key"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
// this function is declared is in the build-tagged setup files
|
||||
verifyRootKeyOnHardware(t, rootKeyID)
|
||||
}
|
||||
return root, signing
|
||||
}
|
||||
|
||||
// Adds the given target to the gun, publishes it, and lists it to ensure that
|
||||
// it appears. Returns the listing output.
|
||||
func assertSuccessfullyPublish(
|
||||
t *testing.T, tempDir, url, gun, target, fname string) string {
|
||||
|
||||
_, err := runCommand(t, tempDir, "add", gun, target, fname)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = runCommand(t, tempDir, "-s", url, "publish", gun)
|
||||
assert.NoError(t, err)
|
||||
|
||||
output, err := runCommand(t, tempDir, "-s", url, "list", gun)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, strings.Contains(string(output), target))
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
// Tests root key generation and key rotation
|
||||
func TestClientKeyGenerationRotation(t *testing.T) {
|
||||
// -- setup --
|
||||
cleanup := setUp(t)
|
||||
defer cleanup()
|
||||
|
||||
tempDir, err := ioutil.TempDir("/tmp", "repo")
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
tempfiles := make([]string, 2)
|
||||
for i := 0; i < 2; i++ {
|
||||
tempFile, err := ioutil.TempFile("/tmp", "targetfile")
|
||||
assert.NoError(t, err)
|
||||
tempFile.Close()
|
||||
tempfiles[i] = tempFile.Name()
|
||||
defer os.Remove(tempFile.Name())
|
||||
}
|
||||
|
||||
server := setupServer()
|
||||
defer server.Close()
|
||||
|
||||
var target = "sdgkadga"
|
||||
|
||||
// -- tests --
|
||||
|
||||
// starts out with no keys
|
||||
assertNumKeys(t, tempDir, 0, 0)
|
||||
|
||||
// generate root key produces a single root key and no other keys
|
||||
_, err = runCommand(t, tempDir, "key", "generate", "ecdsa")
|
||||
assert.NoError(t, err)
|
||||
assertNumKeys(t, tempDir, 1, 0)
|
||||
|
||||
// initialize a repo, should have signing keys and no new root key
|
||||
_, err = runCommand(t, tempDir, "-s", server.URL, "init", "gun")
|
||||
assert.NoError(t, err)
|
||||
origRoot, origSign := assertNumKeys(t, tempDir, 1, 2)
|
||||
|
||||
// publish using the original keys
|
||||
assertSuccessfullyPublish(t, tempDir, server.URL, "gun", target, tempfiles[0])
|
||||
|
||||
// rotate the signing keys
|
||||
_, err = runCommand(t, tempDir, "key", "rotate", "gun")
|
||||
assert.NoError(t, err)
|
||||
root, sign := assertNumKeys(t, tempDir, 1, 4)
|
||||
assert.Equal(t, origRoot[0], root[0])
|
||||
// there should be the new keys and the old keys
|
||||
for _, origKey := range origSign {
|
||||
found := false
|
||||
for _, key := range sign {
|
||||
if key == origKey {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
assert.True(t, found, "Old key not found in list of old and new keys")
|
||||
}
|
||||
|
||||
// publish the key rotation
|
||||
_, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun")
|
||||
assert.NoError(t, err)
|
||||
root, sign = assertNumKeys(t, tempDir, 1, 2)
|
||||
assert.Equal(t, origRoot[0], root[0])
|
||||
// just do a cursory rotation check that the keys aren't equal anymore
|
||||
for _, origKey := range origSign {
|
||||
for _, key := range sign {
|
||||
assert.NotEqual(
|
||||
t, key, origKey, "One of the signing keys was not removed")
|
||||
}
|
||||
}
|
||||
|
||||
// publish using the new keys
|
||||
output := assertSuccessfullyPublish(
|
||||
t, tempDir, server.URL, "gun", target+"2", tempfiles[1])
|
||||
// assert that the previous target is sitll there
|
||||
assert.True(t, strings.Contains(string(output), target))
|
||||
}
|
||||
|
||||
// Tests import/export root+signing keys - repo with imported keys should be
|
||||
// able to publish successfully
|
||||
func TestClientKeyImportExportRootAndSigning(t *testing.T) {
|
||||
// -- setup --
|
||||
cleanup := setUp(t)
|
||||
defer cleanup()
|
||||
|
||||
dirs := make([]string, 3)
|
||||
for i := 0; i < 3; i++ {
|
||||
tempDir, err := ioutil.TempDir("/tmp", "repo")
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(tempDir)
|
||||
dirs[i] = tempDir
|
||||
}
|
||||
|
||||
tempfiles := make([]string, 2)
|
||||
for i := 0; i < 2; i++ {
|
||||
tempFile, err := ioutil.TempFile("/tmp", "tempfile")
|
||||
assert.NoError(t, err)
|
||||
tempFile.Close()
|
||||
tempfiles[i] = tempFile.Name()
|
||||
defer os.Remove(tempFile.Name())
|
||||
}
|
||||
|
||||
server := setupServer()
|
||||
defer server.Close()
|
||||
|
||||
var (
|
||||
target = "sdgkadga"
|
||||
err error
|
||||
)
|
||||
|
||||
// create two repos and publish a target
|
||||
for _, gun := range []string{"gun1", "gun2"} {
|
||||
_, err = runCommand(t, dirs[0], "-s", server.URL, "init", gun)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assertSuccessfullyPublish(
|
||||
t, dirs[0], server.URL, gun, target, tempfiles[0])
|
||||
}
|
||||
assertNumKeys(t, dirs[0], 1, 4)
|
||||
|
||||
// -- tests --
|
||||
zipfile := tempfiles[0] + ".zip"
|
||||
defer os.Remove(zipfile)
|
||||
|
||||
// export then import all keys
|
||||
_, err = runCommand(t, dirs[0], "key", "export", zipfile)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = runCommand(t, dirs[1], "key", "import", zipfile)
|
||||
assert.NoError(t, err)
|
||||
assertNumKeys(t, dirs[1], 1, 4) // all keys should be there
|
||||
|
||||
// can list and publish to both repos using imported keys
|
||||
for _, gun := range []string{"gun1", "gun2"} {
|
||||
output, err := runCommand(t, dirs[1], "-s", server.URL, "list", gun)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, strings.Contains(string(output), target))
|
||||
|
||||
assertSuccessfullyPublish(
|
||||
t, dirs[1], server.URL, gun, target+"2", tempfiles[1])
|
||||
}
|
||||
|
||||
// export then import keys for one gun
|
||||
_, err = runCommand(t, dirs[0], "key", "export", zipfile, "-g", "gun1")
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = runCommand(t, dirs[2], "key", "import", zipfile)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// this function is declared is in the build-tagged setup files
|
||||
if rootOnHardware() {
|
||||
// hardware root is still present, but two signing keys moved - we
|
||||
// can't call assertNumKeys, because in this case it will ONLY be on
|
||||
// hardware and not on disk
|
||||
root, signing := GetKeys(t, dirs[2])
|
||||
assert.Len(t, root, 1)
|
||||
verifyRootKeyOnHardware(t, root[0])
|
||||
assert.Len(t, signing, 2)
|
||||
} else {
|
||||
// only 2 signing keys should be there, and no root key
|
||||
assertNumKeys(t, dirs[2], 0, 2)
|
||||
}
|
||||
}
|
||||
|
||||
// Generate a root key and export the root key only. Return the key ID
|
||||
// exported.
|
||||
func exportRoot(t *testing.T, exportTo string) string {
|
||||
tempDir, err := ioutil.TempDir("/tmp", "repo")
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
// generate root key produces a single root key and no other keys
|
||||
_, err = runCommand(t, tempDir, "key", "generate", "ecdsa")
|
||||
assert.NoError(t, err)
|
||||
oldRoot, _ := assertNumKeys(t, tempDir, 1, 0)
|
||||
|
||||
// export does not require a password
|
||||
oldRetriever := retriever
|
||||
retriever = nil
|
||||
defer func() { // but import will, later
|
||||
retriever = oldRetriever
|
||||
}()
|
||||
|
||||
_, err = runCommand(
|
||||
t, tempDir, "key", "export-root", oldRoot[0], exportTo)
|
||||
assert.NoError(t, err)
|
||||
|
||||
return oldRoot[0]
|
||||
}
|
||||
|
||||
// Tests import/export root key only
|
||||
func TestClientKeyImportExportRootOnly(t *testing.T) {
|
||||
// -- setup --
|
||||
cleanup := setUp(t)
|
||||
defer cleanup()
|
||||
|
||||
tempDir, err := ioutil.TempDir("/tmp", "repo")
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
server := setupServer()
|
||||
defer server.Close()
|
||||
|
||||
var (
|
||||
target = "sdgkadga"
|
||||
rootKeyID string
|
||||
)
|
||||
|
||||
tempFile, err := ioutil.TempFile("/tmp", "pemfile")
|
||||
assert.NoError(t, err)
|
||||
// close later, because we might need to write to it
|
||||
defer os.Remove(tempFile.Name())
|
||||
|
||||
// -- tests --
|
||||
|
||||
if rootOnHardware() {
|
||||
t.Log("Cannot export a key from hardware. Will generate one to import.")
|
||||
|
||||
privKey, err := trustmanager.GenerateECDSAKey(rand.Reader)
|
||||
assert.NoError(t, err)
|
||||
|
||||
pemBytes, err := trustmanager.EncryptPrivateKey(privKey, testPassphrase)
|
||||
assert.NoError(t, err)
|
||||
|
||||
nBytes, err := tempFile.Write(pemBytes)
|
||||
assert.NoError(t, err)
|
||||
tempFile.Close()
|
||||
assert.Equal(t, len(pemBytes), nBytes)
|
||||
rootKeyID = privKey.ID()
|
||||
} else {
|
||||
tempFile.Close()
|
||||
rootKeyID = exportRoot(t, tempFile.Name())
|
||||
}
|
||||
|
||||
// import the key
|
||||
_, err = runCommand(t, tempDir, "key", "import-root", tempFile.Name())
|
||||
assert.NoError(t, err)
|
||||
newRoot, _ := assertNumKeys(t, tempDir, 1, 0)
|
||||
assert.Equal(t, rootKeyID, newRoot[0])
|
||||
|
||||
// Just to make sure, init a repo and publish
|
||||
_, err = runCommand(t, tempDir, "-s", server.URL, "init", "gun")
|
||||
assert.NoError(t, err)
|
||||
assertNumKeys(t, tempDir, 1, 2)
|
||||
assertSuccessfullyPublish(
|
||||
t, tempDir, server.URL, "gun", target, tempFile.Name())
|
||||
}
|
||||
|
||||
func assertNumCerts(t *testing.T, tempDir string, expectedNum int) []string {
|
||||
output, err := runCommand(t, tempDir, "cert", "list")
|
||||
assert.NoError(t, err)
|
||||
certs := splitLines(
|
||||
strings.TrimPrefix(strings.TrimSpace(output), "# Trusted Certificates:"))
|
||||
|
||||
assert.Len(t, certs, expectedNum)
|
||||
return certs
|
||||
}
|
||||
|
||||
// TestClientCertInteraction
|
||||
func TestClientCertInteraction(t *testing.T) {
|
||||
// -- setup --
|
||||
cleanup := setUp(t)
|
||||
defer cleanup()
|
||||
|
||||
tempDir, err := ioutil.TempDir("/tmp", "repo")
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
server := setupServer()
|
||||
defer server.Close()
|
||||
|
||||
// -- tests --
|
||||
_, err = runCommand(t, tempDir, "-s", server.URL, "init", "gun1")
|
||||
assert.NoError(t, err)
|
||||
_, err = runCommand(t, tempDir, "-s", server.URL, "init", "gun2")
|
||||
assert.NoError(t, err)
|
||||
certs := assertNumCerts(t, tempDir, 2)
|
||||
|
||||
// remove certs for one gun
|
||||
_, err = runCommand(t, tempDir, "cert", "remove", "-g", "gun1", "-y")
|
||||
assert.NoError(t, err)
|
||||
certs = assertNumCerts(t, tempDir, 1)
|
||||
|
||||
// remove a single cert
|
||||
certID := strings.TrimSpace(strings.Split(certs[0], " ")[1])
|
||||
// passing an empty gun here because the string for the previous gun has
|
||||
// has already been stored (a drawback of running these commands without)
|
||||
// shelling out
|
||||
_, err = runCommand(t, tempDir, "cert", "remove", certID, "-y", "-g", "")
|
||||
assert.NoError(t, err)
|
||||
assertNumCerts(t, tempDir, 0)
|
||||
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
if testing.Short() {
|
||||
// skip
|
||||
os.Exit(0)
|
||||
}
|
||||
setupCommand(cmd)
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@ package main
|
|||
|
||||
import (
|
||||
"archive/zip"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
|
@ -11,7 +10,6 @@ import (
|
|||
"github.com/docker/notary"
|
||||
notaryclient "github.com/docker/notary/client"
|
||||
"github.com/docker/notary/cryptoservice"
|
||||
"github.com/docker/notary/passphrase"
|
||||
"github.com/docker/notary/signer/api"
|
||||
"github.com/docker/notary/trustmanager"
|
||||
|
||||
|
|
@ -116,16 +114,16 @@ func keysList(cmd *cobra.Command, args []string) {
|
|||
// Get a map of all the keys/roles
|
||||
keysMap := cs.ListAllKeys()
|
||||
|
||||
fmt.Println("")
|
||||
fmt.Println("# Root keys: ")
|
||||
cmd.Println("")
|
||||
cmd.Println("# Root keys: ")
|
||||
for k, v := range keysMap {
|
||||
if v == "root" {
|
||||
fmt.Println(k)
|
||||
cmd.Println(k)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("")
|
||||
fmt.Println("# Signing keys: ")
|
||||
cmd.Println("")
|
||||
cmd.Println("# Signing keys: ")
|
||||
|
||||
// Get a list of all the keys
|
||||
var sortedKeys []string
|
||||
|
|
@ -138,7 +136,7 @@ func keysList(cmd *cobra.Command, args []string) {
|
|||
// Print a sorted list of the key/role
|
||||
for _, k := range sortedKeys {
|
||||
if keysMap[k] != "root" {
|
||||
printKey(k, keysMap[k])
|
||||
printKey(cmd, k, keysMap[k])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -180,7 +178,7 @@ func keysGenerateRootKey(cmd *cobra.Command, args []string) {
|
|||
fatalf("failed to create a new root key: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Generated new %s root key with keyID: %s\n", algorithm, pubKey.ID())
|
||||
cmd.Printf("Generated new %s root key with keyID: %s\n", algorithm, pubKey.ID())
|
||||
}
|
||||
|
||||
// keysExport exports a collection of keys to a ZIP file
|
||||
|
|
@ -208,7 +206,7 @@ func keysExport(cmd *cobra.Command, args []string) {
|
|||
|
||||
// Must use a different passphrase retriever to avoid caching the
|
||||
// unlocking passphrase and reusing that.
|
||||
exportRetriever := passphrase.PromptRetriever()
|
||||
exportRetriever := getRetriever()
|
||||
if keysExportGUN != "" {
|
||||
err = cs.ExportKeysByGUN(exportFile, keysExportGUN, exportRetriever)
|
||||
} else {
|
||||
|
|
@ -253,7 +251,7 @@ func keysExportRoot(cmd *cobra.Command, args []string) {
|
|||
if keysExportRootChangePassphrase {
|
||||
// Must use a different passphrase retriever to avoid caching the
|
||||
// unlocking passphrase and reusing that.
|
||||
exportRetriever := passphrase.PromptRetriever()
|
||||
exportRetriever := getRetriever()
|
||||
err = cs.ExportRootKeyReencrypt(exportFile, keyID, exportRetriever)
|
||||
} else {
|
||||
err = cs.ExportRootKey(exportFile, keyID)
|
||||
|
|
@ -334,10 +332,10 @@ func keysImportRoot(cmd *cobra.Command, args []string) {
|
|||
}
|
||||
}
|
||||
|
||||
func printKey(keyPath, alias string) {
|
||||
func printKey(cmd *cobra.Command, keyPath, alias string) {
|
||||
keyID := filepath.Base(keyPath)
|
||||
gun := filepath.Dir(keyPath)
|
||||
fmt.Printf("%s - %s - %s\n", gun, alias, keyID)
|
||||
cmd.Printf("%s - %s - %s\n", gun, alias, keyID)
|
||||
}
|
||||
|
||||
func keysRotate(cmd *cobra.Command, args []string) {
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ var (
|
|||
configFileName = "config"
|
||||
configFileExt = "json"
|
||||
retriever passphrase.Retriever
|
||||
getRetriever = getPassphraseRetriever
|
||||
mainViper = viper.New()
|
||||
)
|
||||
|
||||
|
|
@ -85,13 +86,7 @@ func parseConfig() {
|
|||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
var notaryCmd = &cobra.Command{
|
||||
Use: "notary",
|
||||
Short: "notary allows the creation of trusted collections.",
|
||||
Long: "notary allows the creation and management of collections of signed targets, allowing the signing and validation of arbitrary content.",
|
||||
}
|
||||
|
||||
func setupCommand(notaryCmd *cobra.Command) {
|
||||
var versionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Print the version number of notary",
|
||||
|
|
@ -124,7 +119,15 @@ func main() {
|
|||
cmdTufLookup.Flags().StringVarP(&remoteTrustServer, "server", "s", "", "Remote trust server location")
|
||||
notaryCmd.AddCommand(cmdVerify)
|
||||
cmdVerify.Flags().StringVarP(&remoteTrustServer, "server", "s", "", "Remote trust server location")
|
||||
}
|
||||
|
||||
func main() {
|
||||
var notaryCmd = &cobra.Command{
|
||||
Use: "notary",
|
||||
Short: "notary allows the creation of trusted collections.",
|
||||
Long: "notary allows the creation and management of collections of signed targets, allowing the signing and validation of arbitrary content.",
|
||||
}
|
||||
setupCommand(notaryCmd)
|
||||
notaryCmd.Execute()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ func tufAdd(cmd *cobra.Command, args []string) {
|
|||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
}
|
||||
fmt.Printf("Addition of %s to %s staged for next publish.\n", targetName, gun)
|
||||
cmd.Printf("Addition of %s to %s staged for next publish.\n", targetName, gun)
|
||||
}
|
||||
|
||||
func tufInit(cmd *cobra.Command, args []string) {
|
||||
|
|
@ -128,7 +128,7 @@ func tufInit(cmd *cobra.Command, args []string) {
|
|||
|
||||
var rootKeyID string
|
||||
if len(rootKeyList) < 1 {
|
||||
fmt.Println("No root keys found. Generating a new root key...")
|
||||
cmd.Println("No root keys found. Generating a new root key...")
|
||||
rootPublicKey, err := nRepo.CryptoService.Create(data.CanonicalRootRole, data.ECDSAKey)
|
||||
rootKeyID = rootPublicKey.ID()
|
||||
if err != nil {
|
||||
|
|
@ -138,7 +138,7 @@ func tufInit(cmd *cobra.Command, args []string) {
|
|||
// Choses the first root key available, which is initialization specific
|
||||
// but should return the HW one first.
|
||||
rootKeyID = rootKeyList[0]
|
||||
fmt.Printf("Root key found, using: %s\n", rootKeyID)
|
||||
cmd.Printf("Root key found, using: %s\n", rootKeyID)
|
||||
}
|
||||
|
||||
err = nRepo.Initialize(rootKeyID)
|
||||
|
|
@ -168,7 +168,7 @@ func tufList(cmd *cobra.Command, args []string) {
|
|||
|
||||
// Print all the available targets
|
||||
for _, t := range targetList {
|
||||
fmt.Printf("%s %x %d\n", t.Name, t.Hashes["sha256"], t.Length)
|
||||
cmd.Printf("%s %x %d\n", t.Name, t.Hashes["sha256"], t.Length)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -191,7 +191,7 @@ func tufLookup(cmd *cobra.Command, args []string) {
|
|||
fatalf(err.Error())
|
||||
}
|
||||
|
||||
fmt.Println(target.Name, fmt.Sprintf("sha256:%x", target.Hashes["sha256"]), target.Length)
|
||||
cmd.Println(target.Name, fmt.Sprintf("sha256:%x", target.Hashes["sha256"]), target.Length)
|
||||
}
|
||||
|
||||
func tufStatus(cmd *cobra.Command, args []string) {
|
||||
|
|
@ -214,15 +214,15 @@ func tufStatus(cmd *cobra.Command, args []string) {
|
|||
}
|
||||
|
||||
if len(cl.List()) == 0 {
|
||||
fmt.Printf("No unpublished changes for %s\n", gun)
|
||||
cmd.Printf("No unpublished changes for %s\n", gun)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Unpublished changes for %s:\n\n", gun)
|
||||
fmt.Printf("%-10s%-10s%-12s%s\n", "action", "scope", "type", "path")
|
||||
fmt.Println("----------------------------------------------------")
|
||||
cmd.Printf("Unpublished changes for %s:\n\n", gun)
|
||||
cmd.Printf("%-10s%-10s%-12s%s\n", "action", "scope", "type", "path")
|
||||
cmd.Println("----------------------------------------------------")
|
||||
for _, ch := range cl.List() {
|
||||
fmt.Printf("%-10s%-10s%-12s%s\n", ch.Action(), ch.Scope(), ch.Type(), ch.Path())
|
||||
cmd.Printf("%-10s%-10s%-12s%s\n", ch.Action(), ch.Scope(), ch.Type(), ch.Path())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -235,7 +235,7 @@ func tufPublish(cmd *cobra.Command, args []string) {
|
|||
gun := args[0]
|
||||
parseConfig()
|
||||
|
||||
fmt.Println("Pushing changes to ", gun, ".")
|
||||
cmd.Println("Pushing changes to", gun)
|
||||
|
||||
nRepo, err := notaryclient.NewNotaryRepository(trustDir, gun, getRemoteTrustServer(), getTransport(gun, false), retriever)
|
||||
if err != nil {
|
||||
|
|
@ -268,7 +268,7 @@ func tufRemove(cmd *cobra.Command, args []string) {
|
|||
fatalf(err.Error())
|
||||
}
|
||||
|
||||
fmt.Printf("Removal of %s from %s staged for next publish.\n", targetName, gun)
|
||||
cmd.Printf("Removal of %s from %s staged for next publish.\n", targetName, gun)
|
||||
}
|
||||
|
||||
func verify(cmd *cobra.Command, args []string) {
|
||||
|
|
|
|||
|
|
@ -25,8 +25,29 @@ const (
|
|||
USER_PIN = "123456"
|
||||
SO_USER_PIN = "010203040506070801020304050607080102030405060708"
|
||||
numSlots = 4 // number of slots in the yubikey
|
||||
|
||||
KeymodeNone = 0
|
||||
KeymodeTouch = 1 // touch enabled
|
||||
KeymodePinOnce = 2 // require pin entry once
|
||||
KeymodePinAlways = 4 // require pin entry all the time
|
||||
)
|
||||
|
||||
// what key mode to use when generating keys
|
||||
var yubikeyKeymode = KeymodeTouch | KeymodePinOnce
|
||||
|
||||
// SetYubikeyKeyMode - sets the mode when generating yubikey keys.
|
||||
// This is to be used for testing. It does nothing if not building with tag
|
||||
// pkcs11.
|
||||
func SetYubikeyKeyMode(keyMode int) error {
|
||||
// technically 7 (1 | 2 | 4) is valid, but KeymodePinOnce +
|
||||
// KeymdoePinAlways don't really make sense together
|
||||
if keyMode < 0 || keyMode > 5 {
|
||||
return errors.New("Invalid key mode")
|
||||
}
|
||||
yubikeyKeymode = keyMode
|
||||
return nil
|
||||
}
|
||||
|
||||
// Hardcoded yubikey PKCS11 ID
|
||||
var YUBIKEY_ROOT_KEY_ID = []byte{2}
|
||||
|
||||
|
|
@ -153,10 +174,7 @@ func addECDSAKey(
|
|||
pkcs11.NewAttribute(pkcs11.CKA_ID, pkcs11KeyID),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_EC_PARAMS, []byte{0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07}),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_VALUE, ecdsaPrivKeyD),
|
||||
// 1 is touch enabled
|
||||
// 2 is pin once
|
||||
// 4 is pin always
|
||||
pkcs11.NewAttribute(pkcs11.CKA_VENDOR_DEFINED, 3),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_VENDOR_DEFINED, yubikeyKeymode),
|
||||
}
|
||||
|
||||
_, err = ctx.CreateObject(session, certTemplate)
|
||||
|
|
@ -641,6 +659,16 @@ func SetupHSMEnv(libraryPath string) (*pkcs11.Ctx, pkcs11.SessionHandle, error)
|
|||
return p, session, nil
|
||||
}
|
||||
|
||||
// YubikeyEnabled returns true if a Yubikey can be accessed
|
||||
func YubikeyAccessible() bool {
|
||||
ctx, session, err := SetupHSMEnv(pkcs11Lib)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer cleanup(ctx, session)
|
||||
return true
|
||||
}
|
||||
|
||||
func login(ctx *pkcs11.Ctx, session pkcs11.SessionHandle, passRetriever passphrase.Retriever, userFlag uint, defaultPassw string) error {
|
||||
// try default password
|
||||
err := ctx.Login(session, userFlag, defaultPassw)
|
||||
|
|
|
|||
|
|
@ -146,6 +146,12 @@ func (tr *Repo) RemoveBaseKeys(role string, keyIDs ...string) error {
|
|||
// remove keys no longer in use by any roles
|
||||
for k := range toDelete {
|
||||
delete(tr.Root.Signed.Keys, k)
|
||||
// remove the signing key from the cryptoservice if it
|
||||
// isn't a root key. Root keys must be kept for rotation
|
||||
// signing
|
||||
if role != data.CanonicalRootRole {
|
||||
tr.cryptoService.RemoveKey(k)
|
||||
}
|
||||
}
|
||||
tr.Root.Dirty = true
|
||||
return nil
|
||||
|
|
|
|||
Loading…
Reference in New Issue