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  }