mirror of https://github.com/docker/docs.git
Refactor the notary command line to not use global mutable state, and to not exit on error.
This way we can test the command more easily (we want to test the error, as opposed to just killing the test). Signed-off-by: Ying Li <ying.li@docker.com>
This commit is contained in:
parent
6acb6a1802
commit
d67a7e128c
|
|
@ -2,65 +2,82 @@ package main
|
|||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"os"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/docker/notary"
|
||||
notaryclient "github.com/docker/notary/client"
|
||||
"github.com/docker/notary/passphrase"
|
||||
"github.com/docker/notary/trustmanager"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func init() {
|
||||
cmdCert.AddCommand(cmdCertList)
|
||||
|
||||
cmdCertRemove.Flags().StringVarP(&certRemoveGUN, "gun", "g", "", "Globally unique name to delete certificates for")
|
||||
cmdCertRemove.Flags().BoolVarP(&certRemoveYes, "yes", "y", false, "Answer yes to the removal question (no confirmation)")
|
||||
cmdCert.AddCommand(cmdCertRemove)
|
||||
}
|
||||
|
||||
var cmdCert = &cobra.Command{
|
||||
var cmdCertTemplate = usageTemplate{
|
||||
Use: "cert",
|
||||
Short: "Operates on certificates.",
|
||||
Long: `Operations on certificates.`,
|
||||
}
|
||||
|
||||
var cmdCertList = &cobra.Command{
|
||||
var cmdCertListTemplate = usageTemplate{
|
||||
Use: "list",
|
||||
Short: "Lists certificates.",
|
||||
Long: "Lists root certificates known to notary.",
|
||||
Run: certList,
|
||||
}
|
||||
|
||||
var certRemoveGUN string
|
||||
var certRemoveYes bool
|
||||
|
||||
var cmdCertRemove = &cobra.Command{
|
||||
var cmdCertRemoveTemplate = usageTemplate{
|
||||
Use: "remove [ certID ]",
|
||||
Short: "Removes the certificate with the given cert ID.",
|
||||
Long: "Remove the certificate with the given cert ID from the local host.",
|
||||
Run: certRemove,
|
||||
}
|
||||
|
||||
type certCommander struct {
|
||||
// these need to be set
|
||||
configGetter func() (*viper.Viper, error)
|
||||
retriever passphrase.Retriever
|
||||
|
||||
// these are for command line parsing - no need to set
|
||||
certRemoveGUN string
|
||||
certRemoveYes bool
|
||||
}
|
||||
|
||||
func (c *certCommander) GetCommand() *cobra.Command {
|
||||
cmd := cmdCertTemplate.ToCommand(nil)
|
||||
cmd.AddCommand(cmdCertListTemplate.ToCommand(c.certList))
|
||||
|
||||
cmdCertRemove := cmdCertRemoveTemplate.ToCommand(c.certRemove)
|
||||
cmdCertRemove.Flags().StringVarP(
|
||||
&c.certRemoveGUN, "gun", "g", "", "Globally unique name to delete certificates for")
|
||||
cmdCertRemove.Flags().BoolVarP(
|
||||
&c.certRemoveYes, "yes", "y", false, "Answer yes to the removal question (no confirmation)")
|
||||
|
||||
cmd.AddCommand(cmdCertRemove)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// certRemove deletes a certificate given a cert ID or a gun
|
||||
// If given a gun, certRemove will also remove local TUF data
|
||||
func certRemove(cmd *cobra.Command, args []string) {
|
||||
func (c *certCommander) certRemove(cmd *cobra.Command, args []string) error {
|
||||
// If the user hasn't provided -g with a gun, or a cert ID, show usage
|
||||
// If the user provided -g and a cert ID, also show usage
|
||||
if (len(args) < 1 && certRemoveGUN == "") || (len(args) > 0 && certRemoveGUN != "") {
|
||||
if (len(args) < 1 && c.certRemoveGUN == "") || (len(args) > 0 && c.certRemoveGUN != "") {
|
||||
cmd.Usage()
|
||||
fatalf("Must specify the cert ID or the GUN of the certificates to remove")
|
||||
return fmt.Errorf("Must specify the cert ID or the GUN of the certificates to remove")
|
||||
}
|
||||
config, err := c.configGetter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parseConfig()
|
||||
|
||||
trustDir := mainViper.GetString("trust_dir")
|
||||
trustDir := config.GetString("trust_dir")
|
||||
certPath := filepath.Join(trustDir, notary.TrustedCertsDir)
|
||||
certStore, err := trustmanager.NewX509FilteredFileStore(
|
||||
certPath,
|
||||
trustmanager.FilterCertsExpiredSha1,
|
||||
)
|
||||
if err != nil {
|
||||
fatalf("Failed to create a new truststore with directory: %s", trustDir)
|
||||
return fmt.Errorf("Failed to create a new truststore with directory: %s", trustDir)
|
||||
}
|
||||
|
||||
var certsToRemove []*x509.Certificate
|
||||
|
|
@ -68,26 +85,26 @@ func certRemove(cmd *cobra.Command, args []string) {
|
|||
var removeTrustData bool
|
||||
|
||||
// If there is no GUN, we expect a cert ID
|
||||
if certRemoveGUN == "" {
|
||||
if c.certRemoveGUN == "" {
|
||||
certID := args[0]
|
||||
// Attempt to find this certificate
|
||||
certFoundByID, err = certStore.GetCertificateByCertID(certID)
|
||||
if err != nil {
|
||||
// This is an invalid ID, the user might have forgotten a character
|
||||
if len(certID) != notary.Sha256HexSize {
|
||||
fatalf("Unable to retrieve certificate with invalid certificate ID provided: %s", certID)
|
||||
return fmt.Errorf("Unable to retrieve certificate with invalid certificate ID provided: %s", certID)
|
||||
}
|
||||
fatalf("Unable to retrieve certificate with cert ID: %s", certID)
|
||||
return fmt.Errorf("Unable to retrieve certificate with cert ID: %s", certID)
|
||||
}
|
||||
// the GUN is the CN from the certificate
|
||||
certRemoveGUN = certFoundByID.Subject.CommonName
|
||||
c.certRemoveGUN = certFoundByID.Subject.CommonName
|
||||
certsToRemove = []*x509.Certificate{certFoundByID}
|
||||
}
|
||||
|
||||
toRemove, err := certStore.GetCertificatesByCN(certRemoveGUN)
|
||||
toRemove, err := certStore.GetCertificatesByCN(c.certRemoveGUN)
|
||||
// We could not find any certificates matching the user's query, so propagate the error
|
||||
if err != nil {
|
||||
fatalf("%v", err)
|
||||
return fmt.Errorf("%v", err)
|
||||
}
|
||||
|
||||
// If we specified a GUN or if the ID we specified is the only certificate with its CN, remove all GUN certs and trust data too
|
||||
|
|
@ -106,48 +123,53 @@ func certRemove(cmd *cobra.Command, args []string) {
|
|||
}
|
||||
// If we were given a GUN or the last ID for a GUN, inform the user that we'll also delete all TUF data
|
||||
if removeTrustData {
|
||||
cmd.Printf("\nAll local trust data will be removed for %s\n", certRemoveGUN)
|
||||
cmd.Printf("\nAll local trust data will be removed for %s\n", c.certRemoveGUN)
|
||||
}
|
||||
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 {
|
||||
if !c.certRemoveYes {
|
||||
confirmed := askConfirm()
|
||||
if !confirmed {
|
||||
fatalf("Aborting action.")
|
||||
return fmt.Errorf("Aborting action.")
|
||||
}
|
||||
}
|
||||
|
||||
if removeTrustData {
|
||||
// Remove all TUF data, so call RemoveTrustData on a NotaryRepository with the GUN
|
||||
// no online operations are performed so the transport argument is nil
|
||||
nRepo, err := notaryclient.NewNotaryRepository(trustDir, certRemoveGUN, getRemoteTrustServer(mainViper), nil, retriever)
|
||||
nRepo, err := notaryclient.NewNotaryRepository(
|
||||
trustDir, c.certRemoveGUN, getRemoteTrustServer(config), nil, c.retriever)
|
||||
if err != nil {
|
||||
fatalf("Could not establish trust data for GUN %s", certRemoveGUN)
|
||||
return fmt.Errorf("Could not establish trust data for GUN %s", c.certRemoveGUN)
|
||||
}
|
||||
// DeleteTrustData will pick up all of the same certificates by GUN (CN) and remove them
|
||||
err = nRepo.DeleteTrustData()
|
||||
if err != nil {
|
||||
fatalf("Failed to delete trust data for %s", certRemoveGUN)
|
||||
return fmt.Errorf("Failed to delete trust data for %s", c.certRemoveGUN)
|
||||
}
|
||||
} else {
|
||||
for _, cert := range certsToRemove {
|
||||
err = certStore.RemoveCert(cert)
|
||||
if err != nil {
|
||||
fatalf("Failed to remove cert %s", cert)
|
||||
return fmt.Errorf("Failed to remove cert %s", cert)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func certList(cmd *cobra.Command, args []string) {
|
||||
func (c *certCommander) certList(cmd *cobra.Command, args []string) error {
|
||||
if len(args) > 0 {
|
||||
cmd.Usage()
|
||||
os.Exit(1)
|
||||
return fmt.Errorf("")
|
||||
}
|
||||
config, err := c.configGetter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parseConfig()
|
||||
|
||||
trustDir := mainViper.GetString("trust_dir")
|
||||
trustDir := config.GetString("trust_dir")
|
||||
certPath := filepath.Join(trustDir, notary.TrustedCertsDir)
|
||||
// Load all individual (non-CA) certificates that aren't expired and don't use SHA1
|
||||
certStore, err := trustmanager.NewX509FilteredFileStore(
|
||||
|
|
@ -155,7 +177,7 @@ func certList(cmd *cobra.Command, args []string) {
|
|||
trustmanager.FilterCertsExpiredSha1,
|
||||
)
|
||||
if err != nil {
|
||||
fatalf("Failed to create a new truststore with directory: %s", trustDir)
|
||||
return fmt.Errorf("Failed to create a new truststore with directory: %s", trustDir)
|
||||
}
|
||||
|
||||
trustedCerts := certStore.GetCertificates()
|
||||
|
|
@ -163,4 +185,5 @@ func certList(cmd *cobra.Command, args []string) {
|
|||
cmd.Println("")
|
||||
prettyPrintCerts(trustedCerts, cmd.Out())
|
||||
cmd.Println("")
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,13 +37,13 @@ var cmdDelegationAddTemplate = usageTemplate{
|
|||
Long: "Add a keys to delegation using the provided public key PEM encoded X509 certificates in a specific Global Unique Name.",
|
||||
}
|
||||
|
||||
var paths []string
|
||||
var removeAll, removeYes bool
|
||||
|
||||
type delegationCommander struct {
|
||||
// these need to be set
|
||||
configGetter func() *viper.Viper
|
||||
configGetter func() (*viper.Viper, error)
|
||||
retriever passphrase.Retriever
|
||||
|
||||
paths []string
|
||||
removeAll, removeYes bool
|
||||
}
|
||||
|
||||
func (d *delegationCommander) GetCommand() *cobra.Command {
|
||||
|
|
@ -51,12 +51,13 @@ func (d *delegationCommander) GetCommand() *cobra.Command {
|
|||
cmd.AddCommand(cmdDelegationListTemplate.ToCommand(d.delegationsList))
|
||||
|
||||
cmdRemDelg := cmdDelegationRemoveTemplate.ToCommand(d.delegationRemove)
|
||||
cmdRemDelg.Flags().StringSliceVar(&paths, "paths", nil, "List of paths to remove")
|
||||
cmdRemDelg.Flags().BoolVarP(&removeYes, "yes", "y", false, "Answer yes to the removal question (no confirmation)")
|
||||
cmdRemDelg.Flags().StringSliceVar(&d.paths, "paths", nil, "List of paths to remove")
|
||||
cmdRemDelg.Flags().BoolVarP(
|
||||
&d.removeYes, "yes", "y", false, "Answer yes to the removal question (no confirmation)")
|
||||
cmd.AddCommand(cmdRemDelg)
|
||||
|
||||
cmdAddDelg := cmdDelegationAddTemplate.ToCommand(d.delegationAdd)
|
||||
cmdAddDelg.Flags().StringSliceVar(&paths, "paths", nil, "List of paths to add")
|
||||
cmdAddDelg.Flags().StringSliceVar(&d.paths, "paths", nil, "List of paths to add")
|
||||
cmd.AddCommand(cmdAddDelg)
|
||||
return cmd
|
||||
}
|
||||
|
|
@ -64,16 +65,26 @@ func (d *delegationCommander) GetCommand() *cobra.Command {
|
|||
// delegationsList lists all the delegations for a particular GUN
|
||||
func (d *delegationCommander) delegationsList(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 1 {
|
||||
cmd.Usage()
|
||||
return fmt.Errorf(
|
||||
"Please provide a Global Unique Name as an argument to list")
|
||||
}
|
||||
|
||||
config := d.configGetter()
|
||||
config, err := d.configGetter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gun := args[0]
|
||||
|
||||
rt, err := getTransport(config, gun, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// initialize repo with transport to get latest state of the world before listing delegations
|
||||
nRepo, err := notaryclient.NewNotaryRepository(config.GetString("trust_dir"), gun, getRemoteTrustServer(config), getTransport(config, gun, true), retriever)
|
||||
nRepo, err := notaryclient.NewNotaryRepository(
|
||||
config.GetString("trust_dir"), gun, getRemoteTrustServer(config), rt, d.retriever)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -92,10 +103,14 @@ func (d *delegationCommander) delegationsList(cmd *cobra.Command, args []string)
|
|||
// delegationRemove removes a public key from a specific role in a GUN
|
||||
func (d *delegationCommander) delegationRemove(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 2 {
|
||||
cmd.Usage()
|
||||
return fmt.Errorf("must specify the Global Unique Name and the role of the delegation along with optional keyIDs and/or a list of paths to remove")
|
||||
}
|
||||
|
||||
config := d.configGetter()
|
||||
config, err := d.configGetter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gun := args[0]
|
||||
role := args[1]
|
||||
|
|
@ -106,14 +121,14 @@ func (d *delegationCommander) delegationRemove(cmd *cobra.Command, args []string
|
|||
}
|
||||
|
||||
// If we're only given the gun and the role, attempt to remove all data for this delegation
|
||||
if len(args) == 2 && paths == nil {
|
||||
removeAll = true
|
||||
if len(args) == 2 && d.paths == nil {
|
||||
d.removeAll = true
|
||||
}
|
||||
|
||||
keyIDs := []string{}
|
||||
// Change nil paths to empty slice for TUF
|
||||
if paths == nil {
|
||||
paths = []string{}
|
||||
if d.paths == nil {
|
||||
d.paths = []string{}
|
||||
}
|
||||
|
||||
if len(args) > 2 {
|
||||
|
|
@ -122,15 +137,16 @@ func (d *delegationCommander) delegationRemove(cmd *cobra.Command, args []string
|
|||
|
||||
// no online operations are performed by add so the transport argument
|
||||
// should be nil
|
||||
nRepo, err := notaryclient.NewNotaryRepository(config.GetString("trust_dir"), gun, getRemoteTrustServer(config), nil, retriever)
|
||||
nRepo, err := notaryclient.NewNotaryRepository(
|
||||
config.GetString("trust_dir"), gun, getRemoteTrustServer(config), nil, d.retriever)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if removeAll {
|
||||
if d.removeAll {
|
||||
cmd.Println("\nAre you sure you want to remove all data for this delegation? (yes/no)")
|
||||
// Ask for confirmation before force removing delegation
|
||||
if !removeYes {
|
||||
if !d.removeYes {
|
||||
confirmed := askConfirm()
|
||||
if !confirmed {
|
||||
fatalf("Aborting action.")
|
||||
|
|
@ -145,19 +161,19 @@ func (d *delegationCommander) delegationRemove(cmd *cobra.Command, args []string
|
|||
}
|
||||
} else {
|
||||
// Remove any keys or paths that we passed in
|
||||
err = nRepo.RemoveDelegationKeysAndPaths(role, keyIDs, paths)
|
||||
err = nRepo.RemoveDelegationKeysAndPaths(role, keyIDs, d.paths)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to remove delegation: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
cmd.Println("")
|
||||
if removeAll {
|
||||
if d.removeAll {
|
||||
cmd.Printf("Forced removal (including all keys and paths) of delegation role %s to repository \"%s\" staged for next publish.\n", role, gun)
|
||||
} else {
|
||||
cmd.Printf(
|
||||
"Removal of delegation role %s with keys %s and paths %s, to repository \"%s\" staged for next publish.\n",
|
||||
role, keyIDs, paths, gun)
|
||||
role, keyIDs, d.paths, gun)
|
||||
}
|
||||
cmd.Println("")
|
||||
|
||||
|
|
@ -166,11 +182,15 @@ func (d *delegationCommander) delegationRemove(cmd *cobra.Command, args []string
|
|||
|
||||
// delegationAdd creates a new delegation by adding a public key from a certificate to a specific role in a GUN
|
||||
func (d *delegationCommander) delegationAdd(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 2 || len(args) < 3 && paths == nil {
|
||||
if len(args) < 2 || len(args) < 3 && d.paths == nil {
|
||||
cmd.Usage()
|
||||
return fmt.Errorf("must specify the Global Unique Name and the role of the delegation along with the public key certificate paths and/or a list of paths to add")
|
||||
}
|
||||
|
||||
config := d.configGetter()
|
||||
config, err := d.configGetter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gun := args[0]
|
||||
role := args[1]
|
||||
|
|
@ -196,13 +216,14 @@ func (d *delegationCommander) delegationAdd(cmd *cobra.Command, args []string) e
|
|||
|
||||
// no online operations are performed by add so the transport argument
|
||||
// should be nil
|
||||
nRepo, err := notaryclient.NewNotaryRepository(config.GetString("trust_dir"), gun, getRemoteTrustServer(config), nil, retriever)
|
||||
nRepo, err := notaryclient.NewNotaryRepository(
|
||||
config.GetString("trust_dir"), gun, getRemoteTrustServer(config), nil, d.retriever)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add the delegation to the repository
|
||||
err = nRepo.AddDelegation(role, pubKeys, paths)
|
||||
err = nRepo.AddDelegation(role, pubKeys, d.paths)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create delegation: %v", err)
|
||||
}
|
||||
|
|
@ -220,7 +241,7 @@ func (d *delegationCommander) delegationAdd(cmd *cobra.Command, args []string) e
|
|||
cmd.Println("")
|
||||
cmd.Printf(
|
||||
"Addition of delegation role %s with keys %s and paths %s, to repository \"%s\" staged for next publish.\n",
|
||||
role, pubKeyIDs, paths, gun)
|
||||
role, pubKeyIDs, d.paths, gun)
|
||||
cmd.Println("")
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,24 +3,25 @@ package main
|
|||
import (
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"github.com/docker/notary/cryptoservice"
|
||||
"github.com/docker/notary/trustmanager"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/notary/cryptoservice"
|
||||
"github.com/docker/notary/trustmanager"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var testTrustDir = "trust_dir"
|
||||
|
||||
func setup() *delegationCommander {
|
||||
return &delegationCommander{
|
||||
configGetter: func() *viper.Viper {
|
||||
configGetter: func() (*viper.Viper, error) {
|
||||
mainViper := viper.New()
|
||||
mainViper.Set("trust_dir", testTrustDir)
|
||||
return mainViper
|
||||
return mainViper, nil
|
||||
},
|
||||
retriever: nil,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,11 +6,16 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/docker/notary/passphrase"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
retriever = passphrase.ConstantRetriever("pass")
|
||||
getRetriever = func() passphrase.Retriever { return retriever }
|
||||
NewNotaryCommand = func() *cobra.Command {
|
||||
commander := ¬aryCommander{
|
||||
getRetriever: func() passphrase.Retriever { return passphrase.ConstantRetriever("pass") },
|
||||
}
|
||||
return commander.GetCommand()
|
||||
}
|
||||
}
|
||||
|
||||
func rootOnHardware() bool {
|
||||
|
|
|
|||
|
|
@ -8,29 +8,37 @@ import (
|
|||
"github.com/docker/notary/passphrase"
|
||||
"github.com/docker/notary/trustmanager/yubikey"
|
||||
"github.com/docker/notary/tuf/data"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var _retriever passphrase.Retriever
|
||||
|
||||
func init() {
|
||||
yubikey.SetYubikeyKeyMode(yubikey.KeymodeNone)
|
||||
regRetriver := passphrase.PromptRetriever()
|
||||
|
||||
retriever = func(k, a string, c bool, n int) (string, bool, error) {
|
||||
regRetriver := passphrase.PromptRetriever()
|
||||
_retriever := func(k, a string, c bool, n int) (string, bool, error) {
|
||||
if k == "Yubikey" {
|
||||
return regRetriver(k, a, c, n)
|
||||
}
|
||||
return testPassphrase, false, nil
|
||||
}
|
||||
|
||||
getRetriever = func() passphrase.Retriever { return retriever }
|
||||
|
||||
// best effort at removing keys here, so nil is fine
|
||||
s, err := yubikey.NewYubiKeyStore(nil, retriever)
|
||||
s, err := yubikey.NewYubiKeyStore(nil, _retriever)
|
||||
if err != nil {
|
||||
for k := range s.ListKeys() {
|
||||
s.RemoveKey(k)
|
||||
}
|
||||
}
|
||||
|
||||
NewNotaryCommand = func() *cobra.Command {
|
||||
commander := ¬aryCommander{
|
||||
getRetriever: func() passphrase.Retriever { return _retriever },
|
||||
}
|
||||
return commander.GetCommand()
|
||||
}
|
||||
}
|
||||
|
||||
var rootOnHardware = yubikey.YubikeyAccessible
|
||||
|
|
@ -38,7 +46,7 @@ var rootOnHardware = yubikey.YubikeyAccessible
|
|||
// Per-test set up deletes all keys on the yubikey
|
||||
func setUp(t *testing.T) {
|
||||
//we're just removing keys here, so nil is fine
|
||||
s, err := yubikey.NewYubiKeyStore(nil, retriever)
|
||||
s, err := yubikey.NewYubiKeyStore(nil, _retriever)
|
||||
assert.NoError(t, err)
|
||||
for k := range s.ListKeys() {
|
||||
err := s.RemoveKey(k)
|
||||
|
|
@ -53,7 +61,7 @@ func verifyRootKeyOnHardware(t *testing.T, rootKeyID string) {
|
|||
// do not bother verifying if there is no yubikey available
|
||||
if yubikey.YubikeyAccessible() {
|
||||
// //we're just getting keys here, so nil is fine
|
||||
s, err := yubikey.NewYubiKeyStore(nil, retriever)
|
||||
s, err := yubikey.NewYubiKeyStore(nil, _retriever)
|
||||
assert.NoError(t, err)
|
||||
privKey, role, err := s.GetKey(rootKeyID)
|
||||
assert.NoError(t, err)
|
||||
|
|
|
|||
|
|
@ -20,31 +20,28 @@ import (
|
|||
"github.com/Sirupsen/logrus"
|
||||
ctxu "github.com/docker/distribution/context"
|
||||
"github.com/docker/notary/cryptoservice"
|
||||
"github.com/docker/notary/passphrase"
|
||||
"github.com/docker/notary/server"
|
||||
"github.com/docker/notary/server/storage"
|
||||
"github.com/docker/notary/trustmanager"
|
||||
"github.com/docker/notary/tuf/data"
|
||||
"github.com/docker/notary/tuf/utils"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var testPassphrase = "passphrase"
|
||||
var NewNotaryCommand func() *cobra.Command
|
||||
|
||||
// run a command and return the output as a string
|
||||
func runCommand(t *testing.T, tempDir string, args ...string) (string, error) {
|
||||
// Using a new viper and Command so we don't have state between command invocations
|
||||
mainViper = viper.New()
|
||||
cmd := &cobra.Command{}
|
||||
setupCommand(cmd)
|
||||
|
||||
b := new(bytes.Buffer)
|
||||
|
||||
// Create an empty config file so we don't load the default on ~/.notary/config.json
|
||||
configFile := filepath.Join(tempDir, "config.json")
|
||||
|
||||
cmd := NewNotaryCommand()
|
||||
cmd.SetArgs(append([]string{"-c", configFile, "-d", tempDir}, args...))
|
||||
cmd.SetOutput(b)
|
||||
retErr := cmd.Execute()
|
||||
|
|
@ -74,7 +71,7 @@ func setupServer() *httptest.Server {
|
|||
ctx = ctxu.WithLogger(ctx, logrus.NewEntry(l))
|
||||
|
||||
cryptoService := cryptoservice.NewCryptoService(
|
||||
"", trustmanager.NewKeyMemoryStore(retriever))
|
||||
"", trustmanager.NewKeyMemoryStore(passphrase.ConstantRetriever("pass")))
|
||||
return httptest.NewServer(server.RootHandler(nil, ctx, cryptoService))
|
||||
}
|
||||
|
||||
|
|
@ -90,7 +87,7 @@ func TestClientTufInteraction(t *testing.T) {
|
|||
server := setupServer()
|
||||
defer server.Close()
|
||||
|
||||
tempFile, err := ioutil.TempFile("/tmp", "targetfile")
|
||||
tempFile, err := ioutil.TempFile("", "targetfile")
|
||||
assert.NoError(t, err)
|
||||
tempFile.Close()
|
||||
defer os.Remove(tempFile.Name())
|
||||
|
|
@ -162,7 +159,7 @@ func TestClientDelegationsInteraction(t *testing.T) {
|
|||
defer server.Close()
|
||||
|
||||
// Setup certificate
|
||||
tempFile, err := ioutil.TempFile("/tmp", "pemfile")
|
||||
tempFile, err := ioutil.TempFile("", "pemfile")
|
||||
assert.NoError(t, err)
|
||||
|
||||
privKey, err := trustmanager.GenerateECDSAKey(rand.Reader)
|
||||
|
|
@ -229,7 +226,7 @@ func TestClientDelegationsInteraction(t *testing.T) {
|
|||
assert.Contains(t, output, keyID)
|
||||
|
||||
// Setup another certificate
|
||||
tempFile2, err := ioutil.TempFile("/tmp", "pemfile2")
|
||||
tempFile2, err := ioutil.TempFile("", "pemfile2")
|
||||
assert.NoError(t, err)
|
||||
|
||||
privKey, err = trustmanager.GenerateECDSAKey(rand.Reader)
|
||||
|
|
@ -391,7 +388,7 @@ func TestClientDelegationsPublishing(t *testing.T) {
|
|||
defer server.Close()
|
||||
|
||||
// Setup certificate for delegation role
|
||||
tempFile, err := ioutil.TempFile("/tmp", "pemfile")
|
||||
tempFile, err := ioutil.TempFile("", "pemfile")
|
||||
assert.NoError(t, err)
|
||||
|
||||
privKey, err := trustmanager.GenerateRSAKey(rand.Reader, 2048)
|
||||
|
|
@ -416,7 +413,7 @@ func TestClientDelegationsPublishing(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
|
||||
// Set up targets for publishing
|
||||
tempTargetFile, err := ioutil.TempFile("/tmp", "targetfile")
|
||||
tempTargetFile, err := ioutil.TempFile("", "targetfile")
|
||||
assert.NoError(t, err)
|
||||
tempTargetFile.Close()
|
||||
defer os.Remove(tempTargetFile.Name())
|
||||
|
|
@ -655,7 +652,7 @@ func TestClientKeyGenerationRotation(t *testing.T) {
|
|||
|
||||
tempfiles := make([]string, 2)
|
||||
for i := 0; i < 2; i++ {
|
||||
tempFile, err := ioutil.TempFile("/tmp", "targetfile")
|
||||
tempFile, err := ioutil.TempFile("", "targetfile")
|
||||
assert.NoError(t, err)
|
||||
tempFile.Close()
|
||||
tempfiles[i] = tempFile.Name()
|
||||
|
|
@ -736,7 +733,7 @@ func TestClientKeyBackupAndRestore(t *testing.T) {
|
|||
|
||||
tempfiles := make([]string, 2)
|
||||
for i := 0; i < 2; i++ {
|
||||
tempFile, err := ioutil.TempFile("/tmp", "tempfile")
|
||||
tempFile, err := ioutil.TempFile("", "tempfile")
|
||||
assert.NoError(t, err)
|
||||
tempFile.Close()
|
||||
tempfiles[i] = tempFile.Name()
|
||||
|
|
@ -815,10 +812,13 @@ func exportRoot(t *testing.T, exportTo string) string {
|
|||
oldRoot, _ := assertNumKeys(t, tempDir, 1, 0, true)
|
||||
|
||||
// export does not require a password
|
||||
oldRetriever := retriever
|
||||
retriever = nil
|
||||
oldNewCommand := NewNotaryCommand
|
||||
NewNotaryCommand = func() *cobra.Command {
|
||||
commander := ¬aryCommander{getRetriever: func() passphrase.Retriever { return nil }}
|
||||
return commander.GetCommand()
|
||||
}
|
||||
defer func() { // but import will, later
|
||||
retriever = oldRetriever
|
||||
NewNotaryCommand = oldNewCommand
|
||||
}()
|
||||
|
||||
_, err = runCommand(
|
||||
|
|
@ -844,7 +844,7 @@ func TestClientKeyImportExportRootOnly(t *testing.T) {
|
|||
rootKeyID string
|
||||
)
|
||||
|
||||
tempFile, err := ioutil.TempFile("/tmp", "pemfile")
|
||||
tempFile, err := ioutil.TempFile("", "pemfile")
|
||||
assert.NoError(t, err)
|
||||
// close later, because we might need to write to it
|
||||
defer os.Remove(tempFile.Name())
|
||||
|
|
@ -1001,22 +1001,23 @@ func TestDefaultRootKeyGeneration(t *testing.T) {
|
|||
// Tests the interaction with the verbose and log-level flags
|
||||
func TestLogLevelFlags(t *testing.T) {
|
||||
// Test default to fatal
|
||||
setVerbosityLevel()
|
||||
n := notaryCommander{}
|
||||
n.setVerbosityLevel()
|
||||
assert.Equal(t, "fatal", logrus.GetLevel().String())
|
||||
|
||||
// Test that verbose (-v) sets to error
|
||||
verbose = true
|
||||
setVerbosityLevel()
|
||||
n.verbose = true
|
||||
n.setVerbosityLevel()
|
||||
assert.Equal(t, "error", logrus.GetLevel().String())
|
||||
|
||||
// Test that debug (-D) sets to debug
|
||||
debug = true
|
||||
setVerbosityLevel()
|
||||
n.debug = true
|
||||
n.setVerbosityLevel()
|
||||
assert.Equal(t, "debug", logrus.GetLevel().String())
|
||||
|
||||
// Test that unsetting verboseError still uses verboseDebug
|
||||
verbose = false
|
||||
setVerbosityLevel()
|
||||
n.verbose = false
|
||||
n.setVerbosityLevel()
|
||||
assert.Equal(t, "debug", logrus.GetLevel().String())
|
||||
}
|
||||
|
||||
|
|
@ -1051,7 +1052,7 @@ func TestClientKeyPassphraseChange(t *testing.T) {
|
|||
}
|
||||
|
||||
func tempDirWithConfig(t *testing.T, config string) string {
|
||||
tempDir, err := ioutil.TempDir("/tmp", "repo")
|
||||
tempDir, err := ioutil.TempDir("", "repo")
|
||||
assert.NoError(t, err)
|
||||
err = ioutil.WriteFile(filepath.Join(tempDir, "config.json"), []byte(config), 0644)
|
||||
assert.NoError(t, err)
|
||||
|
|
|
|||
|
|
@ -22,34 +22,6 @@ import (
|
|||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type usageTemplate struct {
|
||||
Use string
|
||||
Short string
|
||||
Long string
|
||||
}
|
||||
|
||||
type cobraRunE func(cmd *cobra.Command, args []string) error
|
||||
|
||||
func (u usageTemplate) ToCommand(run cobraRunE) *cobra.Command {
|
||||
c := cobra.Command{
|
||||
Use: u.Use,
|
||||
Short: u.Short,
|
||||
Long: u.Long,
|
||||
}
|
||||
if run != nil {
|
||||
// newer versions of cobra support a run function that returns an error,
|
||||
// but in the meantime, this should help ease the transition
|
||||
c.Run = func(cmd *cobra.Command, args []string) {
|
||||
err := run(cmd, args)
|
||||
if err != nil {
|
||||
cmd.Usage()
|
||||
fatalf(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
return &c
|
||||
}
|
||||
|
||||
var cmdKeyTemplate = usageTemplate{
|
||||
Use: "key",
|
||||
Short: "Operates on keys.",
|
||||
|
|
@ -112,8 +84,8 @@ var cmdKeyPasswdTemplate = usageTemplate{
|
|||
|
||||
type keyCommander struct {
|
||||
// these need to be set
|
||||
configGetter func() *viper.Viper
|
||||
retriever passphrase.Retriever
|
||||
configGetter func() (*viper.Viper, error)
|
||||
getRetriever func() passphrase.Retriever
|
||||
|
||||
// these are for command line parsing - no need to set
|
||||
keysExportRootChangePassphrase bool
|
||||
|
|
@ -158,10 +130,14 @@ func (k *keyCommander) GetCommand() *cobra.Command {
|
|||
|
||||
func (k *keyCommander) keysList(cmd *cobra.Command, args []string) error {
|
||||
if len(args) > 0 {
|
||||
cmd.Usage()
|
||||
return fmt.Errorf("")
|
||||
}
|
||||
|
||||
config := k.configGetter()
|
||||
config, err := k.configGetter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ks, err := k.getKeyStores(config, true)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -177,6 +153,7 @@ func (k *keyCommander) keysGenerateRootKey(cmd *cobra.Command, args []string) er
|
|||
// We require one or no arguments (since we have a default value), but if the
|
||||
// user passes in more than one argument, we error out.
|
||||
if len(args) > 1 {
|
||||
cmd.Usage()
|
||||
return fmt.Errorf(
|
||||
"Please provide only one Algorithm as an argument to generate (rsa, ecdsa)")
|
||||
}
|
||||
|
|
@ -198,7 +175,10 @@ func (k *keyCommander) keysGenerateRootKey(cmd *cobra.Command, args []string) er
|
|||
return fmt.Errorf("Algorithm not allowed, possible values are: RSA, ECDSA")
|
||||
}
|
||||
|
||||
config := k.configGetter()
|
||||
config, err := k.configGetter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ks, err := k.getKeyStores(config, true)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -217,10 +197,14 @@ func (k *keyCommander) keysGenerateRootKey(cmd *cobra.Command, args []string) er
|
|||
// keysBackup exports a collection of keys to a ZIP file
|
||||
func (k *keyCommander) keysBackup(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 1 {
|
||||
cmd.Usage()
|
||||
return fmt.Errorf("Must specify output filename for export")
|
||||
}
|
||||
|
||||
config := k.configGetter()
|
||||
config, err := k.configGetter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ks, err := k.getKeyStores(config, false)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -236,7 +220,7 @@ func (k *keyCommander) keysBackup(cmd *cobra.Command, args []string) error {
|
|||
|
||||
// Must use a different passphrase retriever to avoid caching the
|
||||
// unlocking passphrase and reusing that.
|
||||
exportRetriever := getRetriever()
|
||||
exportRetriever := k.getRetriever()
|
||||
if k.keysExportGUN != "" {
|
||||
err = cs.ExportKeysByGUN(exportFile, k.keysExportGUN, exportRetriever)
|
||||
} else {
|
||||
|
|
@ -255,6 +239,7 @@ func (k *keyCommander) keysBackup(cmd *cobra.Command, args []string) error {
|
|||
// keysExportRoot exports a root key by ID to a PEM file
|
||||
func (k *keyCommander) keysExportRoot(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 2 {
|
||||
cmd.Usage()
|
||||
return fmt.Errorf("Must specify key ID and output filename for export")
|
||||
}
|
||||
|
||||
|
|
@ -265,7 +250,10 @@ func (k *keyCommander) keysExportRoot(cmd *cobra.Command, args []string) error {
|
|||
return fmt.Errorf("Please specify a valid root key ID")
|
||||
}
|
||||
|
||||
config := k.configGetter()
|
||||
config, err := k.configGetter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ks, err := k.getKeyStores(config, true)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -279,7 +267,7 @@ func (k *keyCommander) keysExportRoot(cmd *cobra.Command, args []string) error {
|
|||
if k.keysExportRootChangePassphrase {
|
||||
// Must use a different passphrase retriever to avoid caching the
|
||||
// unlocking passphrase and reusing that.
|
||||
exportRetriever := getRetriever()
|
||||
exportRetriever := k.getRetriever()
|
||||
err = cs.ExportRootKeyReencrypt(exportFile, keyID, exportRetriever)
|
||||
} else {
|
||||
err = cs.ExportRootKey(exportFile, keyID)
|
||||
|
|
@ -295,12 +283,16 @@ func (k *keyCommander) keysExportRoot(cmd *cobra.Command, args []string) error {
|
|||
// keysRestore imports keys from a ZIP file
|
||||
func (k *keyCommander) keysRestore(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 1 {
|
||||
cmd.Usage()
|
||||
return fmt.Errorf("Must specify input filename for import")
|
||||
}
|
||||
|
||||
importFilename := args[0]
|
||||
|
||||
config := k.configGetter()
|
||||
config, err := k.configGetter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ks, err := k.getKeyStores(config, true)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -324,10 +316,14 @@ func (k *keyCommander) keysRestore(cmd *cobra.Command, args []string) error {
|
|||
// keysImportRoot imports a root key from a PEM file
|
||||
func (k *keyCommander) keysImportRoot(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 1 {
|
||||
cmd.Usage()
|
||||
return fmt.Errorf("Must specify input filename for import")
|
||||
}
|
||||
|
||||
config := k.configGetter()
|
||||
config, err := k.configGetter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ks, err := k.getKeyStores(config, true)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -352,6 +348,7 @@ func (k *keyCommander) keysImportRoot(cmd *cobra.Command, args []string) error {
|
|||
|
||||
func (k *keyCommander) keysRotate(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 1 {
|
||||
cmd.Usage()
|
||||
return fmt.Errorf("Must specify a GUN")
|
||||
}
|
||||
rotateKeyRole := strings.ToLower(k.rotateKeyRole)
|
||||
|
|
@ -372,18 +369,24 @@ func (k *keyCommander) keysRotate(cmd *cobra.Command, args []string) error {
|
|||
"remote signing/key management is only supported for the snapshot key")
|
||||
}
|
||||
|
||||
config := k.configGetter()
|
||||
config, err := k.configGetter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gun := args[0]
|
||||
var rt http.RoundTripper
|
||||
if k.rotateKeyServerManaged {
|
||||
// this does not actually push the changes, just creates the keys, but
|
||||
// it creates a key remotely so it needs a transport
|
||||
rt = getTransport(config, gun, false)
|
||||
rt, err = getTransport(config, gun, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
nRepo, err := notaryclient.NewNotaryRepository(
|
||||
config.GetString("trust_dir"), gun, getRemoteTrustServer(config),
|
||||
rt, k.retriever)
|
||||
rt, k.getRetriever())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -470,10 +473,14 @@ func removeKeyInteractively(keyStores []trustmanager.KeyStore, keyID string,
|
|||
// keyRemove deletes a private key based on ID
|
||||
func (k *keyCommander) keyRemove(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 1 {
|
||||
cmd.Usage()
|
||||
return fmt.Errorf("must specify the key ID of the key to remove")
|
||||
}
|
||||
|
||||
config := k.configGetter()
|
||||
config, err := k.configGetter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ks, err := k.getKeyStores(config, true)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -494,10 +501,14 @@ func (k *keyCommander) keyRemove(cmd *cobra.Command, args []string) error {
|
|||
// keyPassphraseChange changes the passphrase for a root key's private key based on ID
|
||||
func (k *keyCommander) keyPassphraseChange(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 1 {
|
||||
cmd.Usage()
|
||||
return fmt.Errorf("must specify the key ID of the root key to change the passphrase of")
|
||||
}
|
||||
|
||||
config := k.configGetter()
|
||||
config, err := k.configGetter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ks, err := k.getKeyStores(config, true)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -519,7 +530,7 @@ func (k *keyCommander) keyPassphraseChange(cmd *cobra.Command, args []string) er
|
|||
|
||||
// Must use a different passphrase retriever to avoid caching the
|
||||
// unlocking passphrase and reusing that.
|
||||
passChangeRetriever := getRetriever()
|
||||
passChangeRetriever := k.getRetriever()
|
||||
keyStore, err := trustmanager.NewKeyFileStore(config.GetString("trust_dir"), passChangeRetriever)
|
||||
err = keyStore.AddKey(keyID, role, privKey)
|
||||
if err != nil {
|
||||
|
|
@ -534,9 +545,10 @@ func (k *keyCommander) keyPassphraseChange(cmd *cobra.Command, args []string) er
|
|||
|
||||
func (k *keyCommander) getKeyStores(
|
||||
config *viper.Viper, withHardware bool) ([]trustmanager.KeyStore, error) {
|
||||
retriever := k.getRetriever()
|
||||
|
||||
directory := config.GetString("trust_dir")
|
||||
fileKeyStore, err := trustmanager.NewKeyFileStore(directory, k.retriever)
|
||||
fileKeyStore, err := trustmanager.NewKeyFileStore(directory, retriever)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"Failed to create private key store in directory: %s", directory)
|
||||
|
|
@ -545,7 +557,7 @@ func (k *keyCommander) getKeyStores(
|
|||
ks := []trustmanager.KeyStore{fileKeyStore}
|
||||
|
||||
if withHardware {
|
||||
yubiStore, err := getYubiKeyStore(fileKeyStore, k.retriever)
|
||||
yubiStore, err := getYubiKeyStore(fileKeyStore, retriever)
|
||||
if err == nil && yubiStore != nil {
|
||||
// Note that the order is important, since we want to prioritize
|
||||
// the yubikey store
|
||||
|
|
|
|||
|
|
@ -237,8 +237,8 @@ func TestRotateKeyInvalidRoles(t *testing.T) {
|
|||
for _, role := range invalids {
|
||||
for _, serverManaged := range []bool{true, false} {
|
||||
k := &keyCommander{
|
||||
configGetter: viper.New,
|
||||
retriever: passphrase.ConstantRetriever("pass"),
|
||||
configGetter: func() (*viper.Viper, error) { return viper.New(), nil },
|
||||
getRetriever: func() passphrase.Retriever { return passphrase.ConstantRetriever("pass") },
|
||||
rotateKeyRole: role,
|
||||
rotateKeyServerManaged: serverManaged,
|
||||
}
|
||||
|
|
@ -253,8 +253,8 @@ func TestRotateKeyInvalidRoles(t *testing.T) {
|
|||
// Cannot rotate a targets key and require that the server manage it
|
||||
func TestRotateKeyTargetCannotBeServerManaged(t *testing.T) {
|
||||
k := &keyCommander{
|
||||
configGetter: viper.New,
|
||||
retriever: passphrase.ConstantRetriever("pass"),
|
||||
configGetter: func() (*viper.Viper, error) { return viper.New(), nil },
|
||||
getRetriever: func() passphrase.Retriever { return passphrase.ConstantRetriever("pass") },
|
||||
rotateKeyRole: data.CanonicalTargetsRole,
|
||||
rotateKeyServerManaged: true,
|
||||
}
|
||||
|
|
@ -267,8 +267,8 @@ func TestRotateKeyTargetCannotBeServerManaged(t *testing.T) {
|
|||
// rotate key must be provided with a gun
|
||||
func TestRotateKeyNoGUN(t *testing.T) {
|
||||
k := &keyCommander{
|
||||
configGetter: viper.New,
|
||||
retriever: passphrase.ConstantRetriever("pass"),
|
||||
configGetter: func() (*viper.Viper, error) { return viper.New(), nil },
|
||||
getRetriever: func() passphrase.Retriever { return passphrase.ConstantRetriever("pass") },
|
||||
rotateKeyRole: data.CanonicalTargetsRole,
|
||||
}
|
||||
err := k.keysRotate(&cobra.Command{}, []string{})
|
||||
|
|
@ -321,13 +321,13 @@ func TestRotateKeyRemoteServerManagesKey(t *testing.T) {
|
|||
defer ts.Close()
|
||||
|
||||
k := &keyCommander{
|
||||
configGetter: func() *viper.Viper {
|
||||
configGetter: func() (*viper.Viper, error) {
|
||||
v := viper.New()
|
||||
v.SetDefault("trust_dir", tempBaseDir)
|
||||
v.SetDefault("remote_server.url", ts.URL)
|
||||
return v
|
||||
return v, nil
|
||||
},
|
||||
retriever: ret,
|
||||
getRetriever: func() passphrase.Retriever { return ret },
|
||||
rotateKeyRole: data.CanonicalSnapshotRole,
|
||||
rotateKeyServerManaged: true,
|
||||
}
|
||||
|
|
@ -361,13 +361,13 @@ func TestRotateKeyBothKeys(t *testing.T) {
|
|||
ts.Close()
|
||||
|
||||
k := &keyCommander{
|
||||
configGetter: func() *viper.Viper {
|
||||
configGetter: func() (*viper.Viper, error) {
|
||||
v := viper.New()
|
||||
v.SetDefault("trust_dir", tempBaseDir)
|
||||
// won't need a remote server URL, since we are creating local keys
|
||||
return v
|
||||
return v, nil
|
||||
},
|
||||
retriever: ret,
|
||||
getRetriever: func() passphrase.Retriever { return ret },
|
||||
}
|
||||
err = k.keysRotate(&cobra.Command{}, []string{gun})
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -405,8 +405,8 @@ func TestRotateKeyBothKeys(t *testing.T) {
|
|||
|
||||
func TestChangeKeyPassphraseInvalidID(t *testing.T) {
|
||||
k := &keyCommander{
|
||||
configGetter: viper.New,
|
||||
retriever: passphrase.ConstantRetriever("pass"),
|
||||
configGetter: func() (*viper.Viper, error) { return viper.New(), nil },
|
||||
getRetriever: func() passphrase.Retriever { return passphrase.ConstantRetriever("pass") },
|
||||
}
|
||||
err := k.keyPassphraseChange(&cobra.Command{}, []string{"too_short"})
|
||||
assert.Error(t, err)
|
||||
|
|
@ -415,8 +415,8 @@ func TestChangeKeyPassphraseInvalidID(t *testing.T) {
|
|||
|
||||
func TestChangeKeyPassphraseInvalidNumArgs(t *testing.T) {
|
||||
k := &keyCommander{
|
||||
configGetter: viper.New,
|
||||
retriever: passphrase.ConstantRetriever("pass"),
|
||||
configGetter: func() (*viper.Viper, error) { return viper.New(), nil },
|
||||
getRetriever: func() passphrase.Retriever { return passphrase.ConstantRetriever("pass") },
|
||||
}
|
||||
err := k.keyPassphraseChange(&cobra.Command{}, []string{})
|
||||
assert.Error(t, err)
|
||||
|
|
@ -425,8 +425,8 @@ func TestChangeKeyPassphraseInvalidNumArgs(t *testing.T) {
|
|||
|
||||
func TestChangeKeyPassphraseNonexistentID(t *testing.T) {
|
||||
k := &keyCommander{
|
||||
configGetter: viper.New,
|
||||
retriever: passphrase.ConstantRetriever("pass"),
|
||||
configGetter: func() (*viper.Viper, error) { return viper.New(), nil },
|
||||
getRetriever: func() passphrase.Retriever { return passphrase.ConstantRetriever("pass") },
|
||||
}
|
||||
// Valid ID size, but does not exist as a key ID
|
||||
err := k.keyPassphraseChange(&cobra.Command{}, []string{strings.Repeat("x", notary.Sha256HexSize)})
|
||||
|
|
|
|||
|
|
@ -19,136 +19,166 @@ const (
|
|||
defaultServerURL = "https://notary-server:4443"
|
||||
)
|
||||
|
||||
var (
|
||||
type usageTemplate struct {
|
||||
Use string
|
||||
Short string
|
||||
Long string
|
||||
}
|
||||
|
||||
type cobraRunE func(cmd *cobra.Command, args []string) error
|
||||
|
||||
func (u usageTemplate) ToCommand(run cobraRunE) *cobra.Command {
|
||||
c := cobra.Command{
|
||||
Use: u.Use,
|
||||
Short: u.Short,
|
||||
Long: u.Long,
|
||||
}
|
||||
if run != nil {
|
||||
// newer versions of cobra support a run function that returns an error,
|
||||
// but in the meantime, this should help ease the transition
|
||||
c.RunE = run
|
||||
}
|
||||
return &c
|
||||
}
|
||||
|
||||
type notaryCommander struct {
|
||||
// this needs to be set
|
||||
getRetriever func() passphrase.Retriever
|
||||
|
||||
// these are for command line parsing - no need to set
|
||||
debug bool
|
||||
verbose bool
|
||||
roles []string
|
||||
trustDir string
|
||||
configFile string
|
||||
remoteTrustServer string
|
||||
configPath string
|
||||
configFileName = "config"
|
||||
configFileExt = "json"
|
||||
retriever passphrase.Retriever
|
||||
getRetriever = getPassphraseRetriever
|
||||
mainViper = viper.New()
|
||||
)
|
||||
|
||||
func init() {
|
||||
retriever = getPassphraseRetriever()
|
||||
}
|
||||
|
||||
func parseConfig() *viper.Viper {
|
||||
setVerbosityLevel()
|
||||
func (n *notaryCommander) parseConfig() (*viper.Viper, error) {
|
||||
n.setVerbosityLevel()
|
||||
|
||||
// Get home directory for current user
|
||||
homeDir, err := homedir.Dir()
|
||||
if err != nil {
|
||||
fatalf("Cannot get current user home directory: %v", err)
|
||||
return nil, fmt.Errorf("cannot get current user home directory: %v", err)
|
||||
}
|
||||
if homeDir == "" {
|
||||
fatalf("Cannot get current user home directory")
|
||||
return nil, fmt.Errorf("cannot get current user home directory")
|
||||
}
|
||||
|
||||
config := viper.New()
|
||||
|
||||
// By default our trust directory (where keys are stored) is in ~/.notary/
|
||||
mainViper.SetDefault("trust_dir", filepath.Join(homeDir, filepath.Dir(configDir)))
|
||||
defaultTrustDir := filepath.Join(homeDir, filepath.Dir(configDir))
|
||||
|
||||
// If there was a commandline configFile set, we parse that.
|
||||
// If there wasn't we attempt to find it on the default location ~/.notary/config
|
||||
if configFile != "" {
|
||||
configFileExt = strings.TrimPrefix(filepath.Ext(configFile), ".")
|
||||
configFileName = strings.TrimSuffix(filepath.Base(configFile), filepath.Ext(configFile))
|
||||
configPath = filepath.Dir(configFile)
|
||||
} else {
|
||||
configPath = filepath.Join(homeDir, filepath.Dir(configDir))
|
||||
// If there wasn't we attempt to find it on the default location ~/.notary/config.json
|
||||
configFileName, configFileExt, configPath := "config", "json", defaultTrustDir
|
||||
if n.configFile != "" {
|
||||
configFileExt = strings.TrimPrefix(filepath.Ext(n.configFile), ".")
|
||||
configFileName = strings.TrimSuffix(filepath.Base(n.configFile), filepath.Ext(n.configFile))
|
||||
configPath = filepath.Dir(n.configFile)
|
||||
}
|
||||
|
||||
// Setup the configuration details into viper
|
||||
mainViper.SetConfigName(configFileName)
|
||||
mainViper.SetConfigType(configFileExt)
|
||||
mainViper.AddConfigPath(configPath)
|
||||
config.SetConfigName(configFileName)
|
||||
config.SetConfigType(configFileExt)
|
||||
config.AddConfigPath(configPath)
|
||||
config.SetDefault("trust_dir", defaultTrustDir)
|
||||
config.SetDefault("remote_server", map[string]string{"url": defaultServerURL})
|
||||
|
||||
// Find and read the config file
|
||||
err = mainViper.ReadInConfig()
|
||||
if err != nil {
|
||||
if err := config.ReadInConfig(); err != nil {
|
||||
logrus.Debugf("Configuration file not found, using defaults")
|
||||
// If we were passed in a configFile via -c, bail if it doesn't exist,
|
||||
|
||||
// If we were passed in a configFile via command linen flags, bail if it doesn't exist,
|
||||
// otherwise ignore it: we can use the defaults
|
||||
if configFile != "" || !os.IsNotExist(err) {
|
||||
fatalf("error opening config file %v", err)
|
||||
if n.configFile != "" || !os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("error opening config file: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// At this point we either have the default value or the one set by the config.
|
||||
// Either way, the command-line flag has precedence and overwrites the value
|
||||
if trustDir != "" {
|
||||
mainViper.Set("trust_dir", trustDir)
|
||||
// Either way, some command-line flags have precedence and overwrites the value
|
||||
if n.trustDir != "" {
|
||||
config.Set("trust_dir", n.trustDir)
|
||||
}
|
||||
if n.remoteTrustServer != "" {
|
||||
config.Set("remote_server.url", n.remoteTrustServer)
|
||||
}
|
||||
|
||||
// Expands all the possible ~/ that have been given, either through -d or config
|
||||
// If there is no error, use it, if not, attempt to use whatever the user gave us
|
||||
expandedTrustDir, err := homedir.Expand(mainViper.GetString("trust_dir"))
|
||||
// If there is no error, use it, if not, just attempt to use whatever the user gave us
|
||||
expandedTrustDir, err := homedir.Expand(config.GetString("trust_dir"))
|
||||
if err == nil {
|
||||
mainViper.Set("trust_dir", expandedTrustDir)
|
||||
config.Set("trust_dir", expandedTrustDir)
|
||||
}
|
||||
logrus.Debugf("Using the following trust directory: %s", mainViper.GetString("trust_dir"))
|
||||
logrus.Debugf("Using the following trust directory: %s", config.GetString("trust_dir"))
|
||||
|
||||
return mainViper
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func setupCommand(notaryCmd *cobra.Command) {
|
||||
var versionCmd = &cobra.Command{
|
||||
func (n *notaryCommander) GetCommand() *cobra.Command {
|
||||
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.",
|
||||
SilenceUsage: true,
|
||||
SilenceErrors: true,
|
||||
}
|
||||
notaryCmd.SetOutput(os.Stdout)
|
||||
notaryCmd.AddCommand(&cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Print the version number of notary",
|
||||
Long: `print the version number of notary`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Printf("notary\n Version: %s\n Git commit: %s\n", version.NotaryVersion, version.GitCommit)
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
notaryCmd.AddCommand(versionCmd)
|
||||
|
||||
notaryCmd.PersistentFlags().StringVarP(&trustDir, "trustDir", "d", "", "Directory where the trust data is persisted to")
|
||||
notaryCmd.PersistentFlags().StringVarP(&configFile, "configFile", "c", "", "Path to the configuration file to use")
|
||||
notaryCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Verbose output")
|
||||
notaryCmd.PersistentFlags().BoolVarP(&debug, "debug", "D", false, "Debug output")
|
||||
notaryCmd.PersistentFlags().StringVarP(&remoteTrustServer, "server", "s", "", "Remote trust server location")
|
||||
notaryCmd.PersistentFlags().StringVarP(
|
||||
&n.trustDir, "trustDir", "d", "", "Directory where the trust data is persisted to")
|
||||
notaryCmd.PersistentFlags().StringVarP(
|
||||
&n.configFile, "configFile", "c", "", "Path to the configuration file to use")
|
||||
notaryCmd.PersistentFlags().BoolVarP(&n.verbose, "verbose", "v", false, "Verbose output")
|
||||
notaryCmd.PersistentFlags().BoolVarP(&n.debug, "debug", "D", false, "Debug output")
|
||||
notaryCmd.PersistentFlags().StringVarP(&n.remoteTrustServer, "server", "s", "", "Remote trust server location")
|
||||
|
||||
cmdKeyGenerator := &keyCommander{
|
||||
configGetter: parseConfig,
|
||||
retriever: retriever,
|
||||
configGetter: n.parseConfig,
|
||||
getRetriever: n.getRetriever,
|
||||
}
|
||||
|
||||
cmdDelegationGenerator := &delegationCommander{
|
||||
configGetter: parseConfig,
|
||||
retriever: retriever,
|
||||
configGetter: n.parseConfig,
|
||||
retriever: n.getRetriever(),
|
||||
}
|
||||
|
||||
cmdCertGenerator := &certCommander{
|
||||
configGetter: n.parseConfig,
|
||||
retriever: n.getRetriever(),
|
||||
}
|
||||
|
||||
cmdTufGenerator := &tufCommander{
|
||||
configGetter: n.parseConfig,
|
||||
retriever: n.getRetriever(),
|
||||
}
|
||||
|
||||
notaryCmd.AddCommand(cmdKeyGenerator.GetCommand())
|
||||
notaryCmd.AddCommand(cmdDelegationGenerator.GetCommand())
|
||||
notaryCmd.AddCommand(cmdCert)
|
||||
notaryCmd.AddCommand(cmdTufInit)
|
||||
cmdTufList.Flags().StringSliceVarP(&roles, "roles", "r", nil, "Delegation roles to list targets for (will shadow targets role)")
|
||||
notaryCmd.AddCommand(cmdTufList)
|
||||
cmdTufAdd.Flags().StringSliceVarP(&roles, "roles", "r", nil, "Delegation roles to add this target to")
|
||||
notaryCmd.AddCommand(cmdTufAdd)
|
||||
cmdTufRemove.Flags().StringSliceVarP(&roles, "roles", "r", nil, "Delegation roles to remove this target from")
|
||||
notaryCmd.AddCommand(cmdTufRemove)
|
||||
notaryCmd.AddCommand(cmdTufStatus)
|
||||
notaryCmd.AddCommand(cmdTufPublish)
|
||||
notaryCmd.AddCommand(cmdTufLookup)
|
||||
notaryCmd.AddCommand(cmdTufVerify)
|
||||
notaryCmd.AddCommand(cmdCertGenerator.GetCommand())
|
||||
|
||||
cmdTufGenerator.AddToCommand(¬aryCmd)
|
||||
|
||||
return ¬aryCmd
|
||||
}
|
||||
|
||||
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.",
|
||||
notaryCommander := ¬aryCommander{getRetriever: getPassphraseRetriever}
|
||||
notaryCmd := notaryCommander.GetCommand()
|
||||
if err := notaryCmd.Execute(); err != nil {
|
||||
notaryCmd.Println("")
|
||||
fatalf(err.Error())
|
||||
}
|
||||
notaryCmd.SetOutput(os.Stdout)
|
||||
setupCommand(notaryCmd)
|
||||
notaryCmd.Execute()
|
||||
}
|
||||
|
||||
func fatalf(format string, args ...interface{}) {
|
||||
|
|
@ -185,10 +215,10 @@ func getPassphraseRetriever() passphrase.Retriever {
|
|||
}
|
||||
|
||||
// Set the logging level to fatal on default, or the most specific level the user specified (debug or error)
|
||||
func setVerbosityLevel() {
|
||||
if debug {
|
||||
func (n *notaryCommander) setVerbosityLevel() {
|
||||
if n.debug {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
} else if verbose {
|
||||
} else if n.verbose {
|
||||
logrus.SetLevel(logrus.ErrorLevel)
|
||||
} else {
|
||||
logrus.SetLevel(logrus.FatalLevel)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import (
|
|||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
|
@ -20,74 +19,100 @@ import (
|
|||
"github.com/docker/distribution/registry/client/transport"
|
||||
"github.com/docker/docker/pkg/term"
|
||||
notaryclient "github.com/docker/notary/client"
|
||||
"github.com/docker/notary/passphrase"
|
||||
"github.com/docker/notary/tuf/data"
|
||||
"github.com/docker/notary/utils"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var cmdTufList = &cobra.Command{
|
||||
var cmdTufListTemplate = usageTemplate{
|
||||
Use: "list [ GUN ]",
|
||||
Short: "Lists targets for a remote trusted collection.",
|
||||
Long: "Lists all targets for a remote trusted collection identified by the Globally Unique Name. This is an online operation.",
|
||||
Run: tufList,
|
||||
}
|
||||
|
||||
var cmdTufAdd = &cobra.Command{
|
||||
var cmdTufAddTemplate = usageTemplate{
|
||||
Use: "add [ GUN ] <target> <file>",
|
||||
Short: "Adds the file as a target to the trusted collection.",
|
||||
Long: "Adds the file as a target to the local trusted collection identified by the Globally Unique Name. This is an offline operation. Please then use `publish` to push the changes to the remote trusted collection.",
|
||||
Run: tufAdd,
|
||||
}
|
||||
|
||||
var cmdTufRemove = &cobra.Command{
|
||||
var cmdTufRemoveTemplate = usageTemplate{
|
||||
Use: "remove [ GUN ] <target>",
|
||||
Short: "Removes a target from a trusted collection.",
|
||||
Long: "Removes a target from the local trusted collection identified by the Globally Unique Name. This is an offline operation. Please then use `publish` to push the changes to the remote trusted collection.",
|
||||
Run: tufRemove,
|
||||
}
|
||||
|
||||
var cmdTufInit = &cobra.Command{
|
||||
var cmdTufInitTemplate = usageTemplate{
|
||||
Use: "init [ GUN ]",
|
||||
Short: "Initializes a local trusted collection.",
|
||||
Long: "Initializes a local trusted collection identified by the Globally Unique Name. This is an online operation.",
|
||||
Run: tufInit,
|
||||
}
|
||||
|
||||
var cmdTufLookup = &cobra.Command{
|
||||
var cmdTufLookupTemplate = usageTemplate{
|
||||
Use: "lookup [ GUN ] <target>",
|
||||
Short: "Looks up a specific target in a remote trusted collection.",
|
||||
Long: "Looks up a specific target in a remote trusted collection identified by the Globally Unique Name.",
|
||||
Run: tufLookup,
|
||||
}
|
||||
|
||||
var cmdTufPublish = &cobra.Command{
|
||||
var cmdTufPublishTemplate = usageTemplate{
|
||||
Use: "publish [ GUN ]",
|
||||
Short: "Publishes the local trusted collection.",
|
||||
Long: "Publishes the local trusted collection identified by the Globally Unique Name, sending the local changes to a remote trusted server.",
|
||||
Run: tufPublish,
|
||||
}
|
||||
|
||||
var cmdTufStatus = &cobra.Command{
|
||||
var cmdTufStatusTemplate = usageTemplate{
|
||||
Use: "status [ GUN ]",
|
||||
Short: "Displays status of unpublished changes to the local trusted collection.",
|
||||
Long: "Displays status of unpublished changes to the local trusted collection identified by the Globally Unique Name.",
|
||||
Run: tufStatus,
|
||||
}
|
||||
|
||||
var cmdTufVerify = &cobra.Command{
|
||||
var cmdTufVerifyTemplate = usageTemplate{
|
||||
Use: "verify [ GUN ] <target>",
|
||||
Short: "Verifies if the content is included in the remote trusted collection",
|
||||
Long: "Verifies if the data passed in STDIN is included in the remote trusted collection identified by the Global Unique Name.",
|
||||
Run: tufVerify,
|
||||
}
|
||||
|
||||
func tufAdd(cmd *cobra.Command, args []string) {
|
||||
type tufCommander struct {
|
||||
// these need to be set
|
||||
configGetter func() (*viper.Viper, error)
|
||||
retriever passphrase.Retriever
|
||||
|
||||
// these are for command line parsing - no need to set
|
||||
roles []string
|
||||
}
|
||||
|
||||
func (t *tufCommander) AddToCommand(cmd *cobra.Command) {
|
||||
cmd.AddCommand(cmdTufInitTemplate.ToCommand(t.tufInit))
|
||||
cmd.AddCommand(cmdTufStatusTemplate.ToCommand(t.tufStatus))
|
||||
cmd.AddCommand(cmdTufPublishTemplate.ToCommand(t.tufPublish))
|
||||
cmd.AddCommand(cmdTufLookupTemplate.ToCommand(t.tufLookup))
|
||||
cmd.AddCommand(cmdTufVerifyTemplate.ToCommand(t.tufVerify))
|
||||
|
||||
cmdTufList := cmdTufListTemplate.ToCommand(t.tufList)
|
||||
cmdTufList.Flags().StringSliceVarP(
|
||||
&t.roles, "roles", "r", nil, "Delegation roles to list targets for (will shadow targets role)")
|
||||
cmd.AddCommand(cmdTufList)
|
||||
|
||||
cmdTufAdd := cmdTufAddTemplate.ToCommand(t.tufAdd)
|
||||
cmdTufAdd.Flags().StringSliceVarP(&t.roles, "roles", "r", nil, "Delegation roles to add this target to")
|
||||
cmd.AddCommand(cmdTufAdd)
|
||||
|
||||
cmdTufRemove := cmdTufRemoveTemplate.ToCommand(t.tufRemove)
|
||||
cmdTufRemove.Flags().StringSliceVarP(&t.roles, "roles", "r", nil, "Delegation roles to remove this target from")
|
||||
cmd.AddCommand(cmdTufRemove)
|
||||
}
|
||||
|
||||
func (t *tufCommander) tufAdd(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 3 {
|
||||
cmd.Usage()
|
||||
fatalf("Must specify a GUN, target, and path to target data")
|
||||
return fmt.Errorf("Must specify a GUN, target, and path to target data")
|
||||
}
|
||||
config, err := t.configGetter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parseConfig()
|
||||
|
||||
gun := args[0]
|
||||
targetName := args[1]
|
||||
|
|
@ -95,37 +120,47 @@ func tufAdd(cmd *cobra.Command, args []string) {
|
|||
|
||||
// no online operations are performed by add so the transport argument
|
||||
// should be nil
|
||||
nRepo, err := notaryclient.NewNotaryRepository(mainViper.GetString("trust_dir"), gun, getRemoteTrustServer(mainViper), nil, retriever)
|
||||
nRepo, err := notaryclient.NewNotaryRepository(
|
||||
config.GetString("trust_dir"), gun, getRemoteTrustServer(config), nil, t.retriever)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
target, err := notaryclient.NewTarget(targetName, targetPath)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
return err
|
||||
}
|
||||
// If roles is empty, we default to adding to targets
|
||||
err = nRepo.AddTarget(target, roles...)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
if err = nRepo.AddTarget(target, t.roles...); err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.Printf(
|
||||
"Addition of target \"%s\" to repository \"%s\" staged for next publish.\n",
|
||||
targetName, gun)
|
||||
return nil
|
||||
}
|
||||
|
||||
func tufInit(cmd *cobra.Command, args []string) {
|
||||
func (t *tufCommander) tufInit(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 1 {
|
||||
cmd.Usage()
|
||||
fatalf("Must specify a GUN")
|
||||
return fmt.Errorf("Must specify a GUN")
|
||||
}
|
||||
|
||||
parseConfig()
|
||||
config, err := t.configGetter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gun := args[0]
|
||||
|
||||
nRepo, err := notaryclient.NewNotaryRepository(mainViper.GetString("trust_dir"), gun, getRemoteTrustServer(mainViper), getTransport(mainViper, gun, false), retriever)
|
||||
rt, err := getTransport(config, gun, false)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
nRepo, err := notaryclient.NewNotaryRepository(
|
||||
config.GetString("trust_dir"), gun, getRemoteTrustServer(config), rt, t.retriever)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rootKeyList := nRepo.CryptoService.ListKeys(data.CanonicalRootRole)
|
||||
|
|
@ -136,7 +171,7 @@ func tufInit(cmd *cobra.Command, args []string) {
|
|||
rootPublicKey, err := nRepo.CryptoService.Create(data.CanonicalRootRole, data.ECDSAKey)
|
||||
rootKeyID = rootPublicKey.ID()
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Choses the first root key available, which is initialization specific
|
||||
|
|
@ -145,80 +180,104 @@ func tufInit(cmd *cobra.Command, args []string) {
|
|||
cmd.Printf("Root key found, using: %s\n", rootKeyID)
|
||||
}
|
||||
|
||||
err = nRepo.Initialize(rootKeyID)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
if err = nRepo.Initialize(rootKeyID); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func tufList(cmd *cobra.Command, args []string) {
|
||||
func (t *tufCommander) tufList(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 1 {
|
||||
cmd.Usage()
|
||||
fatalf("Must specify a GUN")
|
||||
return fmt.Errorf("Must specify a GUN")
|
||||
}
|
||||
config, err := t.configGetter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parseConfig()
|
||||
gun := args[0]
|
||||
|
||||
nRepo, err := notaryclient.NewNotaryRepository(mainViper.GetString("trust_dir"), gun, getRemoteTrustServer(mainViper), getTransport(mainViper, gun, true), retriever)
|
||||
rt, err := getTransport(config, gun, true)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
nRepo, err := notaryclient.NewNotaryRepository(
|
||||
config.GetString("trust_dir"), gun, getRemoteTrustServer(config), rt, t.retriever)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Retrieve the remote list of signed targets, prioritizing the passed-in list over targets
|
||||
roles = append(roles, data.CanonicalTargetsRole)
|
||||
roles := append(t.roles, data.CanonicalTargetsRole)
|
||||
targetList, err := nRepo.ListTargets(roles...)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
prettyPrintTargets(targetList, cmd.Out())
|
||||
return nil
|
||||
}
|
||||
|
||||
func tufLookup(cmd *cobra.Command, args []string) {
|
||||
func (t *tufCommander) tufLookup(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 2 {
|
||||
cmd.Usage()
|
||||
fatalf("Must specify a GUN and target")
|
||||
return fmt.Errorf("Must specify a GUN and target")
|
||||
}
|
||||
config, err := t.configGetter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parseConfig()
|
||||
|
||||
gun := args[0]
|
||||
targetName := args[1]
|
||||
|
||||
nRepo, err := notaryclient.NewNotaryRepository(mainViper.GetString("trust_dir"), gun, getRemoteTrustServer(mainViper), getTransport(mainViper, gun, true), retriever)
|
||||
rt, err := getTransport(config, gun, true)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
nRepo, err := notaryclient.NewNotaryRepository(
|
||||
config.GetString("trust_dir"), gun, getRemoteTrustServer(config), rt, t.retriever)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
target, err := nRepo.GetTargetByName(targetName)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Println(target.Name, fmt.Sprintf("sha256:%x", target.Hashes["sha256"]), target.Length)
|
||||
return nil
|
||||
}
|
||||
|
||||
func tufStatus(cmd *cobra.Command, args []string) {
|
||||
func (t *tufCommander) tufStatus(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 1 {
|
||||
cmd.Usage()
|
||||
fatalf("Must specify a GUN")
|
||||
return fmt.Errorf("Must specify a GUN")
|
||||
}
|
||||
|
||||
parseConfig()
|
||||
config, err := t.configGetter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gun := args[0]
|
||||
|
||||
nRepo, err := notaryclient.NewNotaryRepository(mainViper.GetString("trust_dir"), gun, getRemoteTrustServer(mainViper), nil, retriever)
|
||||
nRepo, err := notaryclient.NewNotaryRepository(
|
||||
config.GetString("trust_dir"), gun, getRemoteTrustServer(config), nil, t.retriever)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
cl, err := nRepo.GetChangelist()
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
if len(cl.List()) == 0 {
|
||||
cmd.Printf("No unpublished changes for %s\n", gun)
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
cmd.Printf("Unpublished changes for %s:\n\n", gun)
|
||||
|
|
@ -227,80 +286,102 @@ func tufStatus(cmd *cobra.Command, args []string) {
|
|||
for _, ch := range cl.List() {
|
||||
cmd.Printf("%-10s%-10s%-12s%s\n", ch.Action(), ch.Scope(), ch.Type(), ch.Path())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func tufPublish(cmd *cobra.Command, args []string) {
|
||||
func (t *tufCommander) tufPublish(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 1 {
|
||||
cmd.Usage()
|
||||
fatalf("Must specify a GUN")
|
||||
return fmt.Errorf("Must specify a GUN")
|
||||
}
|
||||
|
||||
parseConfig()
|
||||
config, err := t.configGetter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gun := args[0]
|
||||
|
||||
cmd.Println("Pushing changes to", gun)
|
||||
|
||||
nRepo, err := notaryclient.NewNotaryRepository(mainViper.GetString("trust_dir"), gun, getRemoteTrustServer(mainViper), getTransport(mainViper, gun, false), retriever)
|
||||
rt, err := getTransport(config, gun, false)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
err = nRepo.Publish()
|
||||
nRepo, err := notaryclient.NewNotaryRepository(
|
||||
config.GetString("trust_dir"), gun, getRemoteTrustServer(config), rt, t.retriever)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
if err = nRepo.Publish(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func tufRemove(cmd *cobra.Command, args []string) {
|
||||
func (t *tufCommander) tufRemove(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 2 {
|
||||
cmd.Usage()
|
||||
fatalf("Must specify a GUN and target")
|
||||
return fmt.Errorf("Must specify a GUN and target")
|
||||
}
|
||||
config, err := t.configGetter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parseConfig()
|
||||
|
||||
gun := args[0]
|
||||
targetName := args[1]
|
||||
|
||||
// no online operation are performed by remove so the transport argument
|
||||
// should be nil.
|
||||
repo, err := notaryclient.NewNotaryRepository(mainViper.GetString("trust_dir"), gun, getRemoteTrustServer(mainViper), nil, retriever)
|
||||
repo, err := notaryclient.NewNotaryRepository(
|
||||
config.GetString("trust_dir"), gun, getRemoteTrustServer(config), nil, t.retriever)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
return err
|
||||
}
|
||||
// If roles is empty, we default to removing from targets
|
||||
err = repo.RemoveTarget(targetName, roles...)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
if err = repo.RemoveTarget(targetName, t.roles...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Printf("Removal of %s from %s staged for next publish.\n", targetName, gun)
|
||||
return nil
|
||||
}
|
||||
|
||||
func tufVerify(cmd *cobra.Command, args []string) {
|
||||
func (t *tufCommander) tufVerify(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 2 {
|
||||
cmd.Usage()
|
||||
fatalf("Must specify a GUN and target")
|
||||
return fmt.Errorf("Must specify a GUN and target")
|
||||
}
|
||||
|
||||
parseConfig()
|
||||
config, err := t.configGetter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Reads all of the data on STDIN
|
||||
payload, err := ioutil.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
fatalf("Error reading content from STDIN: %v", err)
|
||||
return fmt.Errorf("Error reading content from STDIN: %v", err)
|
||||
}
|
||||
|
||||
gun := args[0]
|
||||
targetName := args[1]
|
||||
nRepo, err := notaryclient.NewNotaryRepository(mainViper.GetString("trust_dir"), gun, getRemoteTrustServer(mainViper), getTransport(mainViper, gun, true), retriever)
|
||||
|
||||
rt, err := getTransport(config, gun, true)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
nRepo, err := notaryclient.NewNotaryRepository(
|
||||
config.GetString("trust_dir"), gun, getRemoteTrustServer(config), rt, t.retriever)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
target, err := nRepo.GetTargetByName(targetName)
|
||||
if err != nil {
|
||||
logrus.Errorf("error retrieving target by name:%s, error:%v", targetName, err)
|
||||
os.Exit(1)
|
||||
return fmt.Errorf("error retrieving target by name:%s, error:%v", targetName, err)
|
||||
}
|
||||
|
||||
// Create hasher and hash data
|
||||
|
|
@ -308,12 +389,10 @@ func tufVerify(cmd *cobra.Command, args []string) {
|
|||
serverHash := target.Hashes["sha256"]
|
||||
|
||||
if subtle.ConstantTimeCompare(stdinHash[:], serverHash) == 0 {
|
||||
logrus.Error("notary: data not present in the trusted collection.")
|
||||
os.Exit(1)
|
||||
} else {
|
||||
_, _ = os.Stdout.Write(payload)
|
||||
return fmt.Errorf("notary: data not present in the trusted collection")
|
||||
}
|
||||
return
|
||||
_, _ = os.Stdout.Write(payload)
|
||||
return nil
|
||||
}
|
||||
|
||||
type passwordStore struct {
|
||||
|
|
@ -364,16 +443,9 @@ func (ps passwordStore) Basic(u *url.URL) (string, string) {
|
|||
// The readOnly flag indicates if the operation should be performed as an
|
||||
// anonymous read only operation. If the command entered requires write
|
||||
// permissions on the server, readOnly must be false
|
||||
func getTransport(config *viper.Viper, gun string, readOnly bool) http.RoundTripper {
|
||||
func getTransport(config *viper.Viper, gun string, readOnly bool) (http.RoundTripper, error) {
|
||||
// Attempt to get a root CA from the config file. Nil is the host defaults.
|
||||
rootCAFile := config.GetString("remote_server.root_ca")
|
||||
if rootCAFile != "" {
|
||||
// If we haven't been given an Absolute path, we assume it's relative
|
||||
// from the configuration directory (~/.notary by default)
|
||||
if !filepath.IsAbs(rootCAFile) {
|
||||
rootCAFile = filepath.Join(configPath, rootCAFile)
|
||||
}
|
||||
}
|
||||
rootCAFile := utils.GetPathRelativeToConfig(config, "remote_server.root_ca")
|
||||
|
||||
insecureSkipVerify := false
|
||||
if config.IsSet("remote_server.skipTLSVerify") {
|
||||
|
|
@ -403,7 +475,7 @@ func getTransport(config *viper.Viper, gun string, readOnly bool) http.RoundTrip
|
|||
}
|
||||
|
||||
func tokenAuth(trustServerURL string, baseTransport *http.Transport, gun string,
|
||||
readOnly bool) http.RoundTripper {
|
||||
readOnly bool) (http.RoundTripper, error) {
|
||||
|
||||
// TODO(dmcgowan): add notary specific headers
|
||||
authTransport := transport.NewTransport(baseTransport)
|
||||
|
|
@ -413,25 +485,25 @@ func tokenAuth(trustServerURL string, baseTransport *http.Transport, gun string,
|
|||
}
|
||||
endpoint, err := url.Parse(trustServerURL)
|
||||
if err != nil {
|
||||
fatalf("Could not parse remote trust server url (%s): %s", trustServerURL, err.Error())
|
||||
return nil, fmt.Errorf("Could not parse remote trust server url (%s): %s", trustServerURL, err.Error())
|
||||
}
|
||||
if endpoint.Scheme == "" {
|
||||
fatalf("Trust server url has to be in the form of http(s)://URL:PORT. Got: %s", trustServerURL)
|
||||
return nil, fmt.Errorf("Trust server url has to be in the form of http(s)://URL:PORT. Got: %s", trustServerURL)
|
||||
}
|
||||
subPath, err := url.Parse("v2/")
|
||||
if err != nil {
|
||||
fatalf("Failed to parse v2 subpath. This error should not have been reached. Please report it as an issue at https://github.com/docker/notary/issues: %s", err.Error())
|
||||
return nil, fmt.Errorf("Failed to parse v2 subpath. This error should not have been reached. Please report it as an issue at https://github.com/docker/notary/issues: %s", err.Error())
|
||||
}
|
||||
endpoint = endpoint.ResolveReference(subPath)
|
||||
req, err := http.NewRequest("GET", endpoint.String(), nil)
|
||||
if err != nil {
|
||||
fatalf(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
resp, err := pingClient.Do(req)
|
||||
if err != nil {
|
||||
logrus.Errorf("could not reach %s: %s", trustServerURL, err.Error())
|
||||
logrus.Info("continuing in offline mode")
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
// non-nil err means we must close body
|
||||
defer resp.Body.Close()
|
||||
|
|
@ -442,29 +514,24 @@ func tokenAuth(trustServerURL string, baseTransport *http.Transport, gun string,
|
|||
// not a valid status code.
|
||||
logrus.Errorf("could not reach %s: %d", trustServerURL, resp.StatusCode)
|
||||
logrus.Info("continuing in offline mode")
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
challengeManager := auth.NewSimpleChallengeManager()
|
||||
if err := challengeManager.AddResponse(resp); err != nil {
|
||||
fatalf(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ps := passwordStore{anonymous: readOnly}
|
||||
tokenHandler := auth.NewTokenHandler(authTransport, ps, gun, "push", "pull")
|
||||
basicHandler := auth.NewBasicHandler(ps)
|
||||
modifier := transport.RequestModifier(auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler))
|
||||
return transport.NewTransport(baseTransport, modifier)
|
||||
return transport.NewTransport(baseTransport, modifier), nil
|
||||
}
|
||||
|
||||
func getRemoteTrustServer(config *viper.Viper) string {
|
||||
if remoteTrustServer == "" {
|
||||
configRemote := config.GetString("remote_server.url")
|
||||
if configRemote != "" {
|
||||
remoteTrustServer = configRemote
|
||||
} else {
|
||||
remoteTrustServer = defaultServerURL
|
||||
}
|
||||
if configRemote := config.GetString("remote_server.url"); configRemote != "" {
|
||||
return configRemote
|
||||
}
|
||||
return remoteTrustServer
|
||||
return defaultServerURL
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,9 @@ func TestTokenAuth(t *testing.T) {
|
|||
baseTransport = &http.Transport{}
|
||||
gun = "test"
|
||||
)
|
||||
require.Nil(t, tokenAuth("https://localhost:9999", baseTransport, gun, readOnly))
|
||||
auth, err := tokenAuth("https://localhost:9999", baseTransport, gun, readOnly)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, auth)
|
||||
}
|
||||
|
||||
func StatusOKTestHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
@ -31,7 +33,9 @@ func TestTokenAuth200Status(t *testing.T) {
|
|||
s := httptest.NewServer(http.HandlerFunc(NotAuthorizedTestHandler))
|
||||
defer s.Close()
|
||||
|
||||
require.NotNil(t, tokenAuth(s.URL, baseTransport, gun, readOnly))
|
||||
auth, err := tokenAuth(s.URL, baseTransport, gun, readOnly)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, auth)
|
||||
}
|
||||
|
||||
func NotAuthorizedTestHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
@ -47,7 +51,9 @@ func TestTokenAuth401Status(t *testing.T) {
|
|||
s := httptest.NewServer(http.HandlerFunc(NotAuthorizedTestHandler))
|
||||
defer s.Close()
|
||||
|
||||
require.NotNil(t, tokenAuth(s.URL, baseTransport, gun, readOnly))
|
||||
auth, err := tokenAuth(s.URL, baseTransport, gun, readOnly)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, auth)
|
||||
}
|
||||
|
||||
func NotFoundTestHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
@ -63,5 +69,7 @@ func TestTokenAuthNon200Non401Status(t *testing.T) {
|
|||
s := httptest.NewServer(http.HandlerFunc(NotFoundTestHandler))
|
||||
defer s.Close()
|
||||
|
||||
require.Nil(t, tokenAuth(s.URL, baseTransport, gun, readOnly))
|
||||
auth, err := tokenAuth(s.URL, baseTransport, gun, readOnly)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, auth)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue