github.com/vincentwoo/docker@v0.7.3-0.20160116130405-82401a4b13c0/api/client/trust.go (about)

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