docs/cmd/notary/cert.go

190 lines
5.9 KiB
Go

package main
import (
"crypto/x509"
"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"
)
var cmdCertTemplate = usageTemplate{
Use: "cert",
Short: "Operates on certificates.",
Long: `Operations on certificates.`,
}
var cmdCertListTemplate = usageTemplate{
Use: "list",
Short: "Lists certificates.",
Long: "Lists root certificates known to notary.",
}
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.",
}
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 (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 && c.certRemoveGUN == "") || (len(args) > 0 && c.certRemoveGUN != "") {
cmd.Usage()
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
}
trustDir := config.GetString("trust_dir")
certPath := filepath.Join(trustDir, notary.TrustedCertsDir)
certStore, err := trustmanager.NewX509FilteredFileStore(
certPath,
trustmanager.FilterCertsExpiredSha1,
)
if err != nil {
return fmt.Errorf("Failed to create a new truststore with directory: %s", trustDir)
}
var certsToRemove []*x509.Certificate
var certFoundByID *x509.Certificate
var removeTrustData bool
// If there is no GUN, we expect a cert ID
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 {
return fmt.Errorf("Unable to retrieve certificate with invalid certificate ID provided: %s", certID)
}
return fmt.Errorf("Unable to retrieve certificate with cert ID: %s", certID)
}
// the GUN is the CN from the certificate
c.certRemoveGUN = certFoundByID.Subject.CommonName
certsToRemove = []*x509.Certificate{certFoundByID}
}
toRemove, err := certStore.GetCertificatesByCN(c.certRemoveGUN)
// We could not find any certificates matching the user's query, so propagate the error
if err != nil {
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
if certFoundByID == nil || len(toRemove) == 1 {
removeTrustData = true
certsToRemove = toRemove
}
// List all the certificates about to be removed
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)
cmd.Printf("%s - %s\n", cert.Subject.CommonName, certID)
}
// 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", 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 !c.certRemoveYes {
confirmed := askConfirm()
if !confirmed {
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, c.certRemoveGUN, getRemoteTrustServer(config), nil, c.retriever)
if err != nil {
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 {
return fmt.Errorf("Failed to delete trust data for %s", c.certRemoveGUN)
}
} else {
for _, cert := range certsToRemove {
err = certStore.RemoveCert(cert)
if err != nil {
return fmt.Errorf("Failed to remove cert %s", cert)
}
}
}
return nil
}
func (c *certCommander) certList(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
cmd.Usage()
return fmt.Errorf("")
}
config, err := c.configGetter()
if err != nil {
return err
}
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(
certPath,
trustmanager.FilterCertsExpiredSha1,
)
if err != nil {
return fmt.Errorf("Failed to create a new truststore with directory: %s", trustDir)
}
trustedCerts := certStore.GetCertificates()
cmd.Println("")
prettyPrintCerts(trustedCerts, cmd.Out())
cmd.Println("")
return nil
}