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