github.com/itscaro/cli@v0.0.0-20190705081621-c9db0fe93829/cli/command/trust/sign.go (about) 1 package trust 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "path" 8 "sort" 9 "strings" 10 11 "github.com/docker/cli/cli" 12 "github.com/docker/cli/cli/command" 13 "github.com/docker/cli/cli/command/image" 14 "github.com/docker/cli/cli/trust" 15 "github.com/pkg/errors" 16 "github.com/spf13/cobra" 17 "github.com/theupdateframework/notary/client" 18 "github.com/theupdateframework/notary/tuf/data" 19 ) 20 21 type signOptions struct { 22 local bool 23 imageName string 24 } 25 26 func newSignCommand(dockerCli command.Cli) *cobra.Command { 27 options := signOptions{} 28 cmd := &cobra.Command{ 29 Use: "sign IMAGE:TAG", 30 Short: "Sign an image", 31 Args: cli.ExactArgs(1), 32 RunE: func(cmd *cobra.Command, args []string) error { 33 options.imageName = args[0] 34 return runSignImage(dockerCli, options) 35 }, 36 } 37 flags := cmd.Flags() 38 flags.BoolVar(&options.local, "local", false, "Sign a locally tagged image") 39 return cmd 40 } 41 42 func runSignImage(cli command.Cli, options signOptions) error { 43 imageName := options.imageName 44 ctx := context.Background() 45 imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, nil, image.AuthResolver(cli), imageName) 46 if err != nil { 47 return err 48 } 49 if err := validateTag(imgRefAndAuth); err != nil { 50 return err 51 } 52 53 notaryRepo, err := cli.NotaryClient(imgRefAndAuth, trust.ActionsPushAndPull) 54 if err != nil { 55 return trust.NotaryError(imgRefAndAuth.Reference().Name(), err) 56 } 57 if err = clearChangeList(notaryRepo); err != nil { 58 return err 59 } 60 defer clearChangeList(notaryRepo) 61 62 // get the latest repository metadata so we can figure out which roles to sign 63 if _, err = notaryRepo.ListTargets(); err != nil { 64 switch err.(type) { 65 case client.ErrRepoNotInitialized, client.ErrRepositoryNotExist: 66 // before initializing a new repo, check that the image exists locally: 67 if err := checkLocalImageExistence(ctx, cli, imageName); err != nil { 68 return err 69 } 70 71 userRole := data.RoleName(path.Join(data.CanonicalTargetsRole.String(), imgRefAndAuth.AuthConfig().Username)) 72 if err := initNotaryRepoWithSigners(notaryRepo, userRole); err != nil { 73 return trust.NotaryError(imgRefAndAuth.Reference().Name(), err) 74 } 75 76 fmt.Fprintf(cli.Out(), "Created signer: %s\n", imgRefAndAuth.AuthConfig().Username) 77 fmt.Fprintf(cli.Out(), "Finished initializing signed repository for %s\n", imageName) 78 default: 79 return trust.NotaryError(imgRefAndAuth.RepoInfo().Name.Name(), err) 80 } 81 } 82 requestPrivilege := command.RegistryAuthenticationPrivilegedFunc(cli, imgRefAndAuth.RepoInfo().Index, "push") 83 target, err := createTarget(notaryRepo, imgRefAndAuth.Tag()) 84 if err != nil || options.local { 85 switch err := err.(type) { 86 // If the error is nil then the local flag is set 87 case client.ErrNoSuchTarget, client.ErrRepositoryNotExist, nil: 88 // Fail fast if the image doesn't exist locally 89 if err := checkLocalImageExistence(ctx, cli, imageName); err != nil { 90 return err 91 } 92 fmt.Fprintf(cli.Err(), "Signing and pushing trust data for local image %s, may overwrite remote trust data\n", imageName) 93 return image.TrustedPush(ctx, cli, imgRefAndAuth.RepoInfo(), imgRefAndAuth.Reference(), *imgRefAndAuth.AuthConfig(), requestPrivilege) 94 default: 95 return err 96 } 97 } 98 return signAndPublishToTarget(cli.Out(), imgRefAndAuth, notaryRepo, target) 99 } 100 101 func signAndPublishToTarget(out io.Writer, imgRefAndAuth trust.ImageRefAndAuth, notaryRepo client.Repository, target client.Target) error { 102 tag := imgRefAndAuth.Tag() 103 fmt.Fprintf(out, "Signing and pushing trust metadata for %s\n", imgRefAndAuth.Name()) 104 existingSigInfo, err := getExistingSignatureInfoForReleasedTag(notaryRepo, tag) 105 if err != nil { 106 return err 107 } 108 err = image.AddTargetToAllSignableRoles(notaryRepo, &target) 109 if err == nil { 110 prettyPrintExistingSignatureInfo(out, existingSigInfo) 111 err = notaryRepo.Publish() 112 } 113 if err != nil { 114 return errors.Wrapf(err, "failed to sign %s:%s", imgRefAndAuth.RepoInfo().Name.Name(), tag) 115 } 116 fmt.Fprintf(out, "Successfully signed %s:%s\n", imgRefAndAuth.RepoInfo().Name.Name(), tag) 117 return nil 118 } 119 120 func validateTag(imgRefAndAuth trust.ImageRefAndAuth) error { 121 tag := imgRefAndAuth.Tag() 122 if tag == "" { 123 if imgRefAndAuth.Digest() != "" { 124 return fmt.Errorf("cannot use a digest reference for IMAGE:TAG") 125 } 126 return fmt.Errorf("No tag specified for %s", imgRefAndAuth.Name()) 127 } 128 return nil 129 } 130 131 func checkLocalImageExistence(ctx context.Context, cli command.Cli, imageName string) error { 132 _, _, err := cli.Client().ImageInspectWithRaw(ctx, imageName) 133 return err 134 } 135 136 func createTarget(notaryRepo client.Repository, tag string) (client.Target, error) { 137 target := &client.Target{} 138 var err error 139 if tag == "" { 140 return *target, fmt.Errorf("No tag specified") 141 } 142 target.Name = tag 143 target.Hashes, target.Length, err = getSignedManifestHashAndSize(notaryRepo, tag) 144 return *target, err 145 } 146 147 func getSignedManifestHashAndSize(notaryRepo client.Repository, tag string) (data.Hashes, int64, error) { 148 targets, err := notaryRepo.GetAllTargetMetadataByName(tag) 149 if err != nil { 150 return nil, 0, err 151 } 152 return getReleasedTargetHashAndSize(targets, tag) 153 } 154 155 func getReleasedTargetHashAndSize(targets []client.TargetSignedStruct, tag string) (data.Hashes, int64, error) { 156 for _, tgt := range targets { 157 if isReleasedTarget(tgt.Role.Name) { 158 return tgt.Target.Hashes, tgt.Target.Length, nil 159 } 160 } 161 return nil, 0, client.ErrNoSuchTarget(tag) 162 } 163 164 func getExistingSignatureInfoForReleasedTag(notaryRepo client.Repository, tag string) (trustTagRow, error) { 165 targets, err := notaryRepo.GetAllTargetMetadataByName(tag) 166 if err != nil { 167 return trustTagRow{}, err 168 } 169 releasedTargetInfoList := matchReleasedSignatures(targets) 170 if len(releasedTargetInfoList) == 0 { 171 return trustTagRow{}, nil 172 } 173 return releasedTargetInfoList[0], nil 174 } 175 176 func prettyPrintExistingSignatureInfo(out io.Writer, existingSigInfo trustTagRow) { 177 sort.Strings(existingSigInfo.Signers) 178 joinedSigners := strings.Join(existingSigInfo.Signers, ", ") 179 fmt.Fprintf(out, "Existing signatures for tag %s digest %s from:\n%s\n", existingSigInfo.SignedTag, existingSigInfo.Digest, joinedSigners) 180 } 181 182 func initNotaryRepoWithSigners(notaryRepo client.Repository, newSigner data.RoleName) error { 183 rootKey, err := getOrGenerateNotaryKey(notaryRepo, data.CanonicalRootRole) 184 if err != nil { 185 return err 186 } 187 rootKeyID := rootKey.ID() 188 189 // Initialize the notary repository with a remotely managed snapshot key 190 if err := notaryRepo.Initialize([]string{rootKeyID}, data.CanonicalSnapshotRole); err != nil { 191 return err 192 } 193 194 signerKey, err := getOrGenerateNotaryKey(notaryRepo, newSigner) 195 if err != nil { 196 return err 197 } 198 if err := addStagedSigner(notaryRepo, newSigner, []data.PublicKey{signerKey}); err != nil { 199 return errors.Wrapf(err, "could not add signer to repo: %s", strings.TrimPrefix(newSigner.String(), "targets/")) 200 } 201 202 return notaryRepo.Publish() 203 } 204 205 // generates an ECDSA key without a GUN for the specified role 206 func getOrGenerateNotaryKey(notaryRepo client.Repository, role data.RoleName) (data.PublicKey, error) { 207 // use the signer name in the PEM headers if this is a delegation key 208 if data.IsDelegation(role) { 209 role = data.RoleName(notaryRoleToSigner(role)) 210 } 211 keys := notaryRepo.GetCryptoService().ListKeys(role) 212 var err error 213 var key data.PublicKey 214 // always select the first key by ID 215 if len(keys) > 0 { 216 sort.Strings(keys) 217 keyID := keys[0] 218 privKey, _, err := notaryRepo.GetCryptoService().GetPrivateKey(keyID) 219 if err != nil { 220 return nil, err 221 } 222 key = data.PublicKeyFromPrivate(privKey) 223 } else { 224 key, err = notaryRepo.GetCryptoService().Create(role, "", data.ECDSAKey) 225 if err != nil { 226 return nil, err 227 } 228 } 229 return key, nil 230 } 231 232 // stages changes to add a signer with the specified name and key(s). Adds to targets/<name> and targets/releases 233 func addStagedSigner(notaryRepo client.Repository, newSigner data.RoleName, signerKeys []data.PublicKey) error { 234 // create targets/<username> 235 if err := notaryRepo.AddDelegationRoleAndKeys(newSigner, signerKeys); err != nil { 236 return err 237 } 238 if err := notaryRepo.AddDelegationPaths(newSigner, []string{""}); err != nil { 239 return err 240 } 241 242 // create targets/releases 243 if err := notaryRepo.AddDelegationRoleAndKeys(trust.ReleasesRole, signerKeys); err != nil { 244 return err 245 } 246 return notaryRepo.AddDelegationPaths(trust.ReleasesRole, []string{""}) 247 }