github.com/olljanat/moby@v1.13.1/cli/trust/trust.go (about)

     1  package trust
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net"
     7  	"net/http"
     8  	"net/url"
     9  	"os"
    10  	"path"
    11  	"path/filepath"
    12  	"time"
    13  
    14  	"github.com/Sirupsen/logrus"
    15  	"github.com/docker/distribution/registry/client/auth"
    16  	"github.com/docker/distribution/registry/client/auth/challenge"
    17  	"github.com/docker/distribution/registry/client/transport"
    18  	"github.com/docker/docker/api/types"
    19  	registrytypes "github.com/docker/docker/api/types/registry"
    20  	"github.com/docker/docker/cli/command"
    21  	"github.com/docker/docker/cliconfig"
    22  	"github.com/docker/docker/registry"
    23  	"github.com/docker/go-connections/tlsconfig"
    24  	"github.com/docker/notary"
    25  	"github.com/docker/notary/client"
    26  	"github.com/docker/notary/passphrase"
    27  	"github.com/docker/notary/storage"
    28  	"github.com/docker/notary/trustmanager"
    29  	"github.com/docker/notary/trustpinning"
    30  	"github.com/docker/notary/tuf/data"
    31  	"github.com/docker/notary/tuf/signed"
    32  )
    33  
    34  var (
    35  	// ReleasesRole is the role named "releases"
    36  	ReleasesRole = path.Join(data.CanonicalTargetsRole, "releases")
    37  )
    38  
    39  func trustDirectory() string {
    40  	return filepath.Join(cliconfig.ConfigDir(), "trust")
    41  }
    42  
    43  // certificateDirectory returns the directory containing
    44  // TLS certificates for the given server. An error is
    45  // returned if there was an error parsing the server string.
    46  func certificateDirectory(server string) (string, error) {
    47  	u, err := url.Parse(server)
    48  	if err != nil {
    49  		return "", err
    50  	}
    51  
    52  	return filepath.Join(cliconfig.ConfigDir(), "tls", u.Host), nil
    53  }
    54  
    55  // Server returns the base URL for the trust server.
    56  func Server(index *registrytypes.IndexInfo) (string, error) {
    57  	if s := os.Getenv("DOCKER_CONTENT_TRUST_SERVER"); s != "" {
    58  		urlObj, err := url.Parse(s)
    59  		if err != nil || urlObj.Scheme != "https" {
    60  			return "", fmt.Errorf("valid https URL required for trust server, got %s", s)
    61  		}
    62  
    63  		return s, nil
    64  	}
    65  	if index.Official {
    66  		return registry.NotaryServer, nil
    67  	}
    68  	return "https://" + index.Name, nil
    69  }
    70  
    71  type simpleCredentialStore struct {
    72  	auth types.AuthConfig
    73  }
    74  
    75  func (scs simpleCredentialStore) Basic(u *url.URL) (string, string) {
    76  	return scs.auth.Username, scs.auth.Password
    77  }
    78  
    79  func (scs simpleCredentialStore) RefreshToken(u *url.URL, service string) string {
    80  	return scs.auth.IdentityToken
    81  }
    82  
    83  func (scs simpleCredentialStore) SetRefreshToken(*url.URL, string, string) {
    84  }
    85  
    86  // GetNotaryRepository returns a NotaryRepository which stores all the
    87  // information needed to operate on a notary repository.
    88  // It creates an HTTP transport providing authentication support.
    89  func GetNotaryRepository(streams command.Streams, repoInfo *registry.RepositoryInfo, authConfig types.AuthConfig, actions ...string) (*client.NotaryRepository, error) {
    90  	server, err := Server(repoInfo.Index)
    91  	if err != nil {
    92  		return nil, err
    93  	}
    94  
    95  	var cfg = tlsconfig.ClientDefault()
    96  	cfg.InsecureSkipVerify = !repoInfo.Index.Secure
    97  
    98  	// Get certificate base directory
    99  	certDir, err := certificateDirectory(server)
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  	logrus.Debugf("reading certificate directory: %s", certDir)
   104  
   105  	if err := registry.ReadCertsDirectory(cfg, certDir); err != nil {
   106  		return nil, err
   107  	}
   108  
   109  	base := &http.Transport{
   110  		Proxy: http.ProxyFromEnvironment,
   111  		Dial: (&net.Dialer{
   112  			Timeout:   30 * time.Second,
   113  			KeepAlive: 30 * time.Second,
   114  			DualStack: true,
   115  		}).Dial,
   116  		TLSHandshakeTimeout: 10 * time.Second,
   117  		TLSClientConfig:     cfg,
   118  		DisableKeepAlives:   true,
   119  	}
   120  
   121  	// Skip configuration headers since request is not going to Docker daemon
   122  	modifiers := registry.DockerHeaders(command.UserAgent(), http.Header{})
   123  	authTransport := transport.NewTransport(base, modifiers...)
   124  	pingClient := &http.Client{
   125  		Transport: authTransport,
   126  		Timeout:   5 * time.Second,
   127  	}
   128  	endpointStr := server + "/v2/"
   129  	req, err := http.NewRequest("GET", endpointStr, nil)
   130  	if err != nil {
   131  		return nil, err
   132  	}
   133  
   134  	challengeManager := challenge.NewSimpleManager()
   135  
   136  	resp, err := pingClient.Do(req)
   137  	if err != nil {
   138  		// Ignore error on ping to operate in offline mode
   139  		logrus.Debugf("Error pinging notary server %q: %s", endpointStr, err)
   140  	} else {
   141  		defer resp.Body.Close()
   142  
   143  		// Add response to the challenge manager to parse out
   144  		// authentication header and register authentication method
   145  		if err := challengeManager.AddResponse(resp); err != nil {
   146  			return nil, err
   147  		}
   148  	}
   149  
   150  	scope := auth.RepositoryScope{
   151  		Repository: repoInfo.FullName(),
   152  		Actions:    actions,
   153  		Class:      repoInfo.Class,
   154  	}
   155  	creds := simpleCredentialStore{auth: authConfig}
   156  	tokenHandlerOptions := auth.TokenHandlerOptions{
   157  		Transport:   authTransport,
   158  		Credentials: creds,
   159  		Scopes:      []auth.Scope{scope},
   160  		ClientID:    registry.AuthClientID,
   161  	}
   162  	tokenHandler := auth.NewTokenHandlerWithOptions(tokenHandlerOptions)
   163  	basicHandler := auth.NewBasicHandler(creds)
   164  	modifiers = append(modifiers, transport.RequestModifier(auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)))
   165  	tr := transport.NewTransport(base, modifiers...)
   166  
   167  	return client.NewNotaryRepository(
   168  		trustDirectory(),
   169  		repoInfo.FullName(),
   170  		server,
   171  		tr,
   172  		getPassphraseRetriever(streams),
   173  		trustpinning.TrustPinConfig{})
   174  }
   175  
   176  func getPassphraseRetriever(streams command.Streams) notary.PassRetriever {
   177  	aliasMap := map[string]string{
   178  		"root":     "root",
   179  		"snapshot": "repository",
   180  		"targets":  "repository",
   181  		"default":  "repository",
   182  	}
   183  	baseRetriever := passphrase.PromptRetrieverWithInOut(streams.In(), streams.Out(), aliasMap)
   184  	env := map[string]string{
   185  		"root":     os.Getenv("DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE"),
   186  		"snapshot": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
   187  		"targets":  os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
   188  		"default":  os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
   189  	}
   190  
   191  	return func(keyName string, alias string, createNew bool, numAttempts int) (string, bool, error) {
   192  		if v := env[alias]; v != "" {
   193  			return v, numAttempts > 1, nil
   194  		}
   195  		// For non-root roles, we can also try the "default" alias if it is specified
   196  		if v := env["default"]; v != "" && alias != data.CanonicalRootRole {
   197  			return v, numAttempts > 1, nil
   198  		}
   199  		return baseRetriever(keyName, alias, createNew, numAttempts)
   200  	}
   201  }
   202  
   203  // NotaryError formats an error message received from the notary service
   204  func NotaryError(repoName string, err error) error {
   205  	switch err.(type) {
   206  	case *json.SyntaxError:
   207  		logrus.Debugf("Notary syntax error: %s", err)
   208  		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)
   209  	case signed.ErrExpired:
   210  		return fmt.Errorf("Error: remote repository %s out-of-date: %v", repoName, err)
   211  	case trustmanager.ErrKeyNotFound:
   212  		return fmt.Errorf("Error: signing keys for remote repository %s not found: %v", repoName, err)
   213  	case storage.NetworkError:
   214  		return fmt.Errorf("Error: error contacting notary server: %v", err)
   215  	case storage.ErrMetaNotFound:
   216  		return fmt.Errorf("Error: trust data missing for remote repository %s or remote repository not found: %v", repoName, err)
   217  	case trustpinning.ErrRootRotationFail, trustpinning.ErrValidationFail, signed.ErrInvalidKeyType:
   218  		return fmt.Errorf("Warning: potential malicious behavior - trust data mismatch for remote repository %s: %v", repoName, err)
   219  	case signed.ErrNoKeys:
   220  		return fmt.Errorf("Error: could not find signing keys for remote repository %s, or could not decrypt signing key: %v", repoName, err)
   221  	case signed.ErrLowVersion:
   222  		return fmt.Errorf("Warning: potential malicious behavior - trust data version is lower than expected for remote repository %s: %v", repoName, err)
   223  	case signed.ErrRoleThreshold:
   224  		return fmt.Errorf("Warning: potential malicious behavior - trust data has insufficient signatures for remote repository %s: %v", repoName, err)
   225  	case client.ErrRepositoryNotExist:
   226  		return fmt.Errorf("Error: remote trust data does not exist for %s: %v", repoName, err)
   227  	case signed.ErrInsufficientSignatures:
   228  		return fmt.Errorf("Error: could not produce valid signature for %s.  If Yubikey was used, was touch input provided?: %v", repoName, err)
   229  	}
   230  
   231  	return err
   232  }