github.com/walkingsparrow/docker@v1.4.2-0.20151218153551-b708a2249bfa/api/client/trust.go (about)

     1  package client
     2  
     3  import (
     4  	"bufio"
     5  	"encoding/hex"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"net"
    11  	"net/http"
    12  	"net/url"
    13  	"os"
    14  	"path/filepath"
    15  	"regexp"
    16  	"sort"
    17  	"strconv"
    18  	"time"
    19  
    20  	"github.com/Sirupsen/logrus"
    21  	"github.com/docker/distribution/digest"
    22  	"github.com/docker/distribution/registry/client/auth"
    23  	"github.com/docker/distribution/registry/client/transport"
    24  	"github.com/docker/docker/api/client/lib"
    25  	"github.com/docker/docker/api/types"
    26  	registrytypes "github.com/docker/docker/api/types/registry"
    27  	"github.com/docker/docker/cliconfig"
    28  	"github.com/docker/docker/pkg/ansiescape"
    29  	"github.com/docker/docker/pkg/ioutils"
    30  	flag "github.com/docker/docker/pkg/mflag"
    31  	"github.com/docker/docker/pkg/tlsconfig"
    32  	"github.com/docker/docker/reference"
    33  	"github.com/docker/docker/registry"
    34  	"github.com/docker/notary/client"
    35  	"github.com/docker/notary/passphrase"
    36  	"github.com/docker/notary/trustmanager"
    37  	"github.com/docker/notary/tuf/data"
    38  )
    39  
    40  var untrusted bool
    41  
    42  func addTrustedFlags(fs *flag.FlagSet, verify bool) {
    43  	var trusted bool
    44  	if e := os.Getenv("DOCKER_CONTENT_TRUST"); e != "" {
    45  		if t, err := strconv.ParseBool(e); t || err != nil {
    46  			// treat any other value as true
    47  			trusted = true
    48  		}
    49  	}
    50  	message := "Skip image signing"
    51  	if verify {
    52  		message = "Skip image verification"
    53  	}
    54  	fs.BoolVar(&untrusted, []string{"-disable-content-trust"}, !trusted, message)
    55  }
    56  
    57  func isTrusted() bool {
    58  	return !untrusted
    59  }
    60  
    61  var targetRegexp = regexp.MustCompile(`([\S]+): digest: ([\S]+) size: ([\d]+)`)
    62  
    63  type target struct {
    64  	reference registry.Reference
    65  	digest    digest.Digest
    66  	size      int64
    67  }
    68  
    69  func (cli *DockerCli) trustDirectory() string {
    70  	return filepath.Join(cliconfig.ConfigDir(), "trust")
    71  }
    72  
    73  // certificateDirectory returns the directory containing
    74  // TLS certificates for the given server. An error is
    75  // returned if there was an error parsing the server string.
    76  func (cli *DockerCli) certificateDirectory(server string) (string, error) {
    77  	u, err := url.Parse(server)
    78  	if err != nil {
    79  		return "", err
    80  	}
    81  
    82  	return filepath.Join(cliconfig.ConfigDir(), "tls", u.Host), nil
    83  }
    84  
    85  func trustServer(index *registrytypes.IndexInfo) (string, error) {
    86  	if s := os.Getenv("DOCKER_CONTENT_TRUST_SERVER"); s != "" {
    87  		urlObj, err := url.Parse(s)
    88  		if err != nil || urlObj.Scheme != "https" {
    89  			return "", fmt.Errorf("valid https URL required for trust server, got %s", s)
    90  		}
    91  
    92  		return s, nil
    93  	}
    94  	if index.Official {
    95  		return registry.NotaryServer, nil
    96  	}
    97  	return "https://" + index.Name, nil
    98  }
    99  
   100  type simpleCredentialStore struct {
   101  	auth types.AuthConfig
   102  }
   103  
   104  func (scs simpleCredentialStore) Basic(u *url.URL) (string, string) {
   105  	return scs.auth.Username, scs.auth.Password
   106  }
   107  
   108  func (cli *DockerCli) getNotaryRepository(repoInfo *registry.RepositoryInfo, authConfig types.AuthConfig) (*client.NotaryRepository, error) {
   109  	server, err := trustServer(repoInfo.Index)
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  
   114  	var cfg = tlsconfig.ClientDefault
   115  	cfg.InsecureSkipVerify = !repoInfo.Index.Secure
   116  
   117  	// Get certificate base directory
   118  	certDir, err := cli.certificateDirectory(server)
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  	logrus.Debugf("reading certificate directory: %s", certDir)
   123  
   124  	if err := registry.ReadCertsDirectory(&cfg, certDir); err != nil {
   125  		return nil, err
   126  	}
   127  
   128  	base := &http.Transport{
   129  		Proxy: http.ProxyFromEnvironment,
   130  		Dial: (&net.Dialer{
   131  			Timeout:   30 * time.Second,
   132  			KeepAlive: 30 * time.Second,
   133  			DualStack: true,
   134  		}).Dial,
   135  		TLSHandshakeTimeout: 10 * time.Second,
   136  		TLSClientConfig:     &cfg,
   137  		DisableKeepAlives:   true,
   138  	}
   139  
   140  	// Skip configuration headers since request is not going to Docker daemon
   141  	modifiers := registry.DockerHeaders(http.Header{})
   142  	authTransport := transport.NewTransport(base, modifiers...)
   143  	pingClient := &http.Client{
   144  		Transport: authTransport,
   145  		Timeout:   5 * time.Second,
   146  	}
   147  	endpointStr := server + "/v2/"
   148  	req, err := http.NewRequest("GET", endpointStr, nil)
   149  	if err != nil {
   150  		return nil, err
   151  	}
   152  
   153  	challengeManager := auth.NewSimpleChallengeManager()
   154  
   155  	resp, err := pingClient.Do(req)
   156  	if err != nil {
   157  		// Ignore error on ping to operate in offline mode
   158  		logrus.Debugf("Error pinging notary server %q: %s", endpointStr, err)
   159  	} else {
   160  		defer resp.Body.Close()
   161  
   162  		// Add response to the challenge manager to parse out
   163  		// authentication header and register authentication method
   164  		if err := challengeManager.AddResponse(resp); err != nil {
   165  			return nil, err
   166  		}
   167  	}
   168  
   169  	creds := simpleCredentialStore{auth: authConfig}
   170  	tokenHandler := auth.NewTokenHandler(authTransport, creds, repoInfo.FullName(), "push", "pull")
   171  	basicHandler := auth.NewBasicHandler(creds)
   172  	modifiers = append(modifiers, transport.RequestModifier(auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)))
   173  	tr := transport.NewTransport(base, modifiers...)
   174  
   175  	return client.NewNotaryRepository(cli.trustDirectory(), repoInfo.FullName(), server, tr, cli.getPassphraseRetriever())
   176  }
   177  
   178  func convertTarget(t client.Target) (target, error) {
   179  	h, ok := t.Hashes["sha256"]
   180  	if !ok {
   181  		return target{}, errors.New("no valid hash, expecting sha256")
   182  	}
   183  	return target{
   184  		reference: registry.ParseReference(t.Name),
   185  		digest:    digest.NewDigestFromHex("sha256", hex.EncodeToString(h)),
   186  		size:      t.Length,
   187  	}, nil
   188  }
   189  
   190  func (cli *DockerCli) getPassphraseRetriever() passphrase.Retriever {
   191  	aliasMap := map[string]string{
   192  		"root":     "root",
   193  		"snapshot": "repository",
   194  		"targets":  "repository",
   195  	}
   196  	baseRetriever := passphrase.PromptRetrieverWithInOut(cli.in, cli.out, aliasMap)
   197  	env := map[string]string{
   198  		"root":     os.Getenv("DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE"),
   199  		"snapshot": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
   200  		"targets":  os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
   201  	}
   202  
   203  	// Backwards compatibility with old env names. We should remove this in 1.10
   204  	if env["root"] == "" {
   205  		if passphrase := os.Getenv("DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE"); passphrase != "" {
   206  			env["root"] = passphrase
   207  			fmt.Fprintf(cli.err, "[DEPRECATED] The environment variable DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE has been deprecated and will be removed in v1.10. Please use DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE\n")
   208  		}
   209  	}
   210  	if env["snapshot"] == "" || env["targets"] == "" {
   211  		if passphrase := os.Getenv("DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE"); passphrase != "" {
   212  			env["snapshot"] = passphrase
   213  			env["targets"] = passphrase
   214  			fmt.Fprintf(cli.err, "[DEPRECATED] The environment variable DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE has been deprecated and will be removed in v1.10. Please use DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE\n")
   215  		}
   216  	}
   217  
   218  	return func(keyName string, alias string, createNew bool, numAttempts int) (string, bool, error) {
   219  		if v := env[alias]; v != "" {
   220  			return v, numAttempts > 1, nil
   221  		}
   222  		return baseRetriever(keyName, alias, createNew, numAttempts)
   223  	}
   224  }
   225  
   226  func (cli *DockerCli) trustedReference(ref reference.NamedTagged) (reference.Canonical, error) {
   227  	repoInfo, err := registry.ParseRepositoryInfo(ref)
   228  	if err != nil {
   229  		return nil, err
   230  	}
   231  
   232  	// Resolve the Auth config relevant for this server
   233  	authConfig := registry.ResolveAuthConfig(cli.configFile.AuthConfigs, repoInfo.Index)
   234  
   235  	notaryRepo, err := cli.getNotaryRepository(repoInfo, authConfig)
   236  	if err != nil {
   237  		fmt.Fprintf(cli.out, "Error establishing connection to trust repository: %s\n", err)
   238  		return nil, err
   239  	}
   240  
   241  	t, err := notaryRepo.GetTargetByName(ref.Tag())
   242  	if err != nil {
   243  		return nil, err
   244  	}
   245  	r, err := convertTarget(*t)
   246  	if err != nil {
   247  		return nil, err
   248  
   249  	}
   250  
   251  	return reference.WithDigest(ref, r.digest)
   252  }
   253  
   254  func (cli *DockerCli) tagTrusted(trustedRef reference.Canonical, ref reference.NamedTagged) error {
   255  	fmt.Fprintf(cli.out, "Tagging %s as %s\n", trustedRef.String(), ref.String())
   256  
   257  	options := types.ImageTagOptions{
   258  		ImageID:        trustedRef.String(),
   259  		RepositoryName: trustedRef.Name(),
   260  		Tag:            ref.Tag(),
   261  		Force:          true,
   262  	}
   263  
   264  	return cli.client.ImageTag(options)
   265  }
   266  
   267  func notaryError(err error) error {
   268  	switch err.(type) {
   269  	case *json.SyntaxError:
   270  		logrus.Debugf("Notary syntax error: %s", err)
   271  		return errors.New("no trust data available for remote repository")
   272  	case client.ErrExpired:
   273  		return fmt.Errorf("remote repository out-of-date: %v", err)
   274  	case trustmanager.ErrKeyNotFound:
   275  		return fmt.Errorf("signing keys not found: %v", err)
   276  	case *net.OpError:
   277  		return fmt.Errorf("error contacting notary server: %v", err)
   278  	}
   279  
   280  	return err
   281  }
   282  
   283  func (cli *DockerCli) trustedPull(repoInfo *registry.RepositoryInfo, ref registry.Reference, authConfig types.AuthConfig, requestPrivilege lib.RequestPrivilegeFunc) error {
   284  	var refs []target
   285  
   286  	notaryRepo, err := cli.getNotaryRepository(repoInfo, authConfig)
   287  	if err != nil {
   288  		fmt.Fprintf(cli.out, "Error establishing connection to trust repository: %s\n", err)
   289  		return err
   290  	}
   291  
   292  	if ref.String() == "" {
   293  		// List all targets
   294  		targets, err := notaryRepo.ListTargets()
   295  		if err != nil {
   296  			return notaryError(err)
   297  		}
   298  		for _, tgt := range targets {
   299  			t, err := convertTarget(*tgt)
   300  			if err != nil {
   301  				fmt.Fprintf(cli.out, "Skipping target for %q\n", repoInfo.Name())
   302  				continue
   303  			}
   304  			refs = append(refs, t)
   305  		}
   306  	} else {
   307  		t, err := notaryRepo.GetTargetByName(ref.String())
   308  		if err != nil {
   309  			return notaryError(err)
   310  		}
   311  		r, err := convertTarget(*t)
   312  		if err != nil {
   313  			return err
   314  
   315  		}
   316  		refs = append(refs, r)
   317  	}
   318  
   319  	for i, r := range refs {
   320  		displayTag := r.reference.String()
   321  		if displayTag != "" {
   322  			displayTag = ":" + displayTag
   323  		}
   324  		fmt.Fprintf(cli.out, "Pull (%d of %d): %s%s@%s\n", i+1, len(refs), repoInfo.Name(), displayTag, r.digest)
   325  
   326  		if err := cli.imagePullPrivileged(authConfig, repoInfo.Name(), r.digest.String(), requestPrivilege); err != nil {
   327  			return err
   328  		}
   329  
   330  		// If reference is not trusted, tag by trusted reference
   331  		if !r.reference.HasDigest() {
   332  			tagged, err := reference.WithTag(repoInfo, r.reference.String())
   333  			if err != nil {
   334  				return err
   335  			}
   336  			trustedRef, err := reference.WithDigest(repoInfo, r.digest)
   337  			if err != nil {
   338  				return err
   339  			}
   340  			if err := cli.tagTrusted(trustedRef, tagged); err != nil {
   341  				return err
   342  			}
   343  		}
   344  	}
   345  	return nil
   346  }
   347  
   348  func targetStream(in io.Writer) (io.WriteCloser, <-chan []target) {
   349  	r, w := io.Pipe()
   350  	out := io.MultiWriter(in, w)
   351  	targetChan := make(chan []target)
   352  
   353  	go func() {
   354  		targets := []target{}
   355  		scanner := bufio.NewScanner(r)
   356  		scanner.Split(ansiescape.ScanANSILines)
   357  		for scanner.Scan() {
   358  			line := scanner.Bytes()
   359  			if matches := targetRegexp.FindSubmatch(line); len(matches) == 4 {
   360  				dgst, err := digest.ParseDigest(string(matches[2]))
   361  				if err != nil {
   362  					// Line does match what is expected, continue looking for valid lines
   363  					logrus.Debugf("Bad digest value %q in matched line, ignoring\n", string(matches[2]))
   364  					continue
   365  				}
   366  				s, err := strconv.ParseInt(string(matches[3]), 10, 64)
   367  				if err != nil {
   368  					// Line does match what is expected, continue looking for valid lines
   369  					logrus.Debugf("Bad size value %q in matched line, ignoring\n", string(matches[3]))
   370  					continue
   371  				}
   372  
   373  				targets = append(targets, target{
   374  					reference: registry.ParseReference(string(matches[1])),
   375  					digest:    dgst,
   376  					size:      s,
   377  				})
   378  			}
   379  		}
   380  		targetChan <- targets
   381  	}()
   382  
   383  	return ioutils.NewWriteCloserWrapper(out, w.Close), targetChan
   384  }
   385  
   386  func (cli *DockerCli) trustedPush(repoInfo *registry.RepositoryInfo, tag string, authConfig types.AuthConfig, requestPrivilege lib.RequestPrivilegeFunc) error {
   387  	streamOut, targetChan := targetStream(cli.out)
   388  
   389  	reqError := cli.imagePushPrivileged(authConfig, repoInfo.Name(), tag, streamOut, requestPrivilege)
   390  
   391  	// Close stream channel to finish target parsing
   392  	if err := streamOut.Close(); err != nil {
   393  		return err
   394  	}
   395  	// Check error from request
   396  	if reqError != nil {
   397  		return reqError
   398  	}
   399  
   400  	// Get target results
   401  	targets := <-targetChan
   402  
   403  	if tag == "" {
   404  		fmt.Fprintf(cli.out, "No tag specified, skipping trust metadata push\n")
   405  		return nil
   406  	}
   407  	if len(targets) == 0 {
   408  		fmt.Fprintf(cli.out, "No targets found, skipping trust metadata push\n")
   409  		return nil
   410  	}
   411  
   412  	fmt.Fprintf(cli.out, "Signing and pushing trust metadata\n")
   413  
   414  	repo, err := cli.getNotaryRepository(repoInfo, authConfig)
   415  	if err != nil {
   416  		fmt.Fprintf(cli.out, "Error establishing connection to notary repository: %s\n", err)
   417  		return err
   418  	}
   419  
   420  	for _, target := range targets {
   421  		h, err := hex.DecodeString(target.digest.Hex())
   422  		if err != nil {
   423  			return err
   424  		}
   425  		t := &client.Target{
   426  			Name: target.reference.String(),
   427  			Hashes: data.Hashes{
   428  				string(target.digest.Algorithm()): h,
   429  			},
   430  			Length: int64(target.size),
   431  		}
   432  		if err := repo.AddTarget(t); err != nil {
   433  			return err
   434  		}
   435  	}
   436  
   437  	err = repo.Publish()
   438  	if _, ok := err.(*client.ErrRepoNotInitialized); !ok {
   439  		return notaryError(err)
   440  	}
   441  
   442  	keys := repo.CryptoService.ListKeys(data.CanonicalRootRole)
   443  
   444  	var rootKeyID string
   445  	// always select the first root key
   446  	if len(keys) > 0 {
   447  		sort.Strings(keys)
   448  		rootKeyID = keys[0]
   449  	} else {
   450  		rootPublicKey, err := repo.CryptoService.Create(data.CanonicalRootRole, data.ECDSAKey)
   451  		if err != nil {
   452  			return err
   453  		}
   454  		rootKeyID = rootPublicKey.ID()
   455  	}
   456  
   457  	if err := repo.Initialize(rootKeyID); err != nil {
   458  		return notaryError(err)
   459  	}
   460  	fmt.Fprintf(cli.out, "Finished initializing %q\n", repoInfo.FullName())
   461  
   462  	return notaryError(repo.Publish())
   463  }