github.com/justincormack/cli@v0.0.0-20201215022714-831ebeae9675/cli/command/trust/signer_add.go (about) 1 package trust 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "os" 9 "path" 10 "regexp" 11 "strings" 12 13 "github.com/docker/cli/cli" 14 "github.com/docker/cli/cli/command" 15 "github.com/docker/cli/cli/command/image" 16 "github.com/docker/cli/cli/trust" 17 "github.com/docker/cli/opts" 18 "github.com/pkg/errors" 19 "github.com/spf13/cobra" 20 "github.com/theupdateframework/notary/client" 21 "github.com/theupdateframework/notary/tuf/data" 22 tufutils "github.com/theupdateframework/notary/tuf/utils" 23 ) 24 25 type signerAddOptions struct { 26 keys opts.ListOpts 27 signer string 28 repos []string 29 } 30 31 func newSignerAddCommand(dockerCli command.Cli) *cobra.Command { 32 var options signerAddOptions 33 cmd := &cobra.Command{ 34 Use: "add OPTIONS NAME REPOSITORY [REPOSITORY...] ", 35 Short: "Add a signer", 36 Args: cli.RequiresMinArgs(2), 37 RunE: func(cmd *cobra.Command, args []string) error { 38 options.signer = args[0] 39 options.repos = args[1:] 40 return addSigner(dockerCli, options) 41 }, 42 } 43 flags := cmd.Flags() 44 options.keys = opts.NewListOpts(nil) 45 flags.Var(&options.keys, "key", "Path to the signer's public key file") 46 return cmd 47 } 48 49 var validSignerName = regexp.MustCompile(`^[a-z0-9][a-z0-9\_\-]*$`).MatchString 50 51 func addSigner(cli command.Cli, options signerAddOptions) error { 52 signerName := options.signer 53 if !validSignerName(signerName) { 54 return fmt.Errorf("signer name \"%s\" must start with lowercase alphanumeric characters and can include \"-\" or \"_\" after the first character", signerName) 55 } 56 if signerName == "releases" { 57 return fmt.Errorf("releases is a reserved keyword, please use a different signer name") 58 } 59 60 if options.keys.Len() == 0 { 61 return fmt.Errorf("path to a public key must be provided using the `--key` flag") 62 } 63 signerPubKeys, err := ingestPublicKeys(options.keys.GetAll()) 64 if err != nil { 65 return err 66 } 67 var errRepos []string 68 for _, repoName := range options.repos { 69 fmt.Fprintf(cli.Out(), "Adding signer \"%s\" to %s...\n", signerName, repoName) 70 if err := addSignerToRepo(cli, signerName, repoName, signerPubKeys); err != nil { 71 fmt.Fprintln(cli.Err(), err.Error()+"\n") 72 errRepos = append(errRepos, repoName) 73 } else { 74 fmt.Fprintf(cli.Out(), "Successfully added signer: %s to %s\n\n", signerName, repoName) 75 } 76 } 77 if len(errRepos) > 0 { 78 return fmt.Errorf("Failed to add signer to: %s", strings.Join(errRepos, ", ")) 79 } 80 return nil 81 } 82 83 func addSignerToRepo(cli command.Cli, signerName string, repoName string, signerPubKeys []data.PublicKey) error { 84 ctx := context.Background() 85 imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, nil, image.AuthResolver(cli), repoName) 86 if err != nil { 87 return err 88 } 89 90 notaryRepo, err := cli.NotaryClient(imgRefAndAuth, trust.ActionsPushAndPull) 91 if err != nil { 92 return trust.NotaryError(imgRefAndAuth.Reference().Name(), err) 93 } 94 95 if _, err = notaryRepo.ListTargets(); err != nil { 96 switch err.(type) { 97 case client.ErrRepoNotInitialized, client.ErrRepositoryNotExist: 98 fmt.Fprintf(cli.Out(), "Initializing signed repository for %s...\n", repoName) 99 if err := getOrGenerateRootKeyAndInitRepo(notaryRepo); err != nil { 100 return trust.NotaryError(repoName, err) 101 } 102 fmt.Fprintf(cli.Out(), "Successfully initialized %q\n", repoName) 103 default: 104 return trust.NotaryError(repoName, err) 105 } 106 } 107 108 newSignerRoleName := data.RoleName(path.Join(data.CanonicalTargetsRole.String(), signerName)) 109 110 if err := addStagedSigner(notaryRepo, newSignerRoleName, signerPubKeys); err != nil { 111 return errors.Wrapf(err, "could not add signer to repo: %s", strings.TrimPrefix(newSignerRoleName.String(), "targets/")) 112 } 113 114 return notaryRepo.Publish() 115 } 116 117 func ingestPublicKeys(pubKeyPaths []string) ([]data.PublicKey, error) { 118 pubKeys := []data.PublicKey{} 119 for _, pubKeyPath := range pubKeyPaths { 120 // Read public key bytes from PEM file, limit to 1 KiB 121 pubKeyFile, err := os.OpenFile(pubKeyPath, os.O_RDONLY, 0666) 122 if err != nil { 123 return nil, errors.Wrap(err, "unable to read public key from file") 124 } 125 defer pubKeyFile.Close() 126 // limit to 127 l := io.LimitReader(pubKeyFile, 1<<20) 128 pubKeyBytes, err := ioutil.ReadAll(l) 129 if err != nil { 130 return nil, errors.Wrap(err, "unable to read public key from file") 131 } 132 133 // Parse PEM bytes into type PublicKey 134 pubKey, err := tufutils.ParsePEMPublicKey(pubKeyBytes) 135 if err != nil { 136 return nil, errors.Wrapf(err, "could not parse public key from file: %s", pubKeyPath) 137 } 138 pubKeys = append(pubKeys, pubKey) 139 } 140 return pubKeys, nil 141 }