mirror of
				https://github.com/moby/moby.git
				synced 2022-11-09 12:21:53 -05:00 
			
		
		
		
	cli: Split out GetNotaryRepository and associated functions
Split these into cli/trust so that other commands can make use of them. Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
This commit is contained in:
		
							parent
							
								
									b6f870a278
								
							
						
					
					
						commit
						4b8c79f25e
					
				
					 3 changed files with 244 additions and 227 deletions
				
			
		| 
						 | 
				
			
			@ -6,43 +6,22 @@ import (
 | 
			
		|||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/docker/distribution/digest"
 | 
			
		||||
	"github.com/docker/distribution/registry/client/auth"
 | 
			
		||||
	"github.com/docker/distribution/registry/client/auth/challenge"
 | 
			
		||||
	"github.com/docker/distribution/registry/client/transport"
 | 
			
		||||
	"github.com/docker/docker/api/types"
 | 
			
		||||
	registrytypes "github.com/docker/docker/api/types/registry"
 | 
			
		||||
	"github.com/docker/docker/cli/command"
 | 
			
		||||
	"github.com/docker/docker/cliconfig"
 | 
			
		||||
	"github.com/docker/docker/cli/trust"
 | 
			
		||||
	"github.com/docker/docker/distribution"
 | 
			
		||||
	"github.com/docker/docker/pkg/jsonmessage"
 | 
			
		||||
	"github.com/docker/docker/reference"
 | 
			
		||||
	"github.com/docker/docker/registry"
 | 
			
		||||
	"github.com/docker/go-connections/tlsconfig"
 | 
			
		||||
	"github.com/docker/notary"
 | 
			
		||||
	"github.com/docker/notary/client"
 | 
			
		||||
	"github.com/docker/notary/passphrase"
 | 
			
		||||
	"github.com/docker/notary/storage"
 | 
			
		||||
	"github.com/docker/notary/trustmanager"
 | 
			
		||||
	"github.com/docker/notary/trustpinning"
 | 
			
		||||
	"github.com/docker/notary/tuf/data"
 | 
			
		||||
	"github.com/docker/notary/tuf/signed"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	releasesRole = path.Join(data.CanonicalTargetsRole, "releases")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type target struct {
 | 
			
		||||
| 
						 | 
				
			
			@ -118,7 +97,7 @@ func trustedPush(ctx context.Context, cli *command.DockerCli, repoInfo *registry
 | 
			
		|||
 | 
			
		||||
	fmt.Fprintln(cli.Out(), "Signing and pushing trust metadata")
 | 
			
		||||
 | 
			
		||||
	repo, err := GetNotaryRepository(cli, repoInfo, authConfig, "push", "pull")
 | 
			
		||||
	repo, err := trust.GetNotaryRepository(cli, repoInfo, authConfig, "push", "pull")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Fprintf(cli.Out(), "Error establishing connection to notary repository: %s\n", err)
 | 
			
		||||
		return err
 | 
			
		||||
| 
						 | 
				
			
			@ -145,7 +124,7 @@ func trustedPush(ctx context.Context, cli *command.DockerCli, repoInfo *registry
 | 
			
		|||
 | 
			
		||||
		// Initialize the notary repository with a remotely managed snapshot key
 | 
			
		||||
		if err := repo.Initialize([]string{rootKeyID}, data.CanonicalSnapshotRole); err != nil {
 | 
			
		||||
			return notaryError(repoInfo.FullName(), err)
 | 
			
		||||
			return trust.NotaryError(repoInfo.FullName(), err)
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Fprintf(cli.Out(), "Finished initializing %q\n", repoInfo.FullName())
 | 
			
		||||
		err = repo.AddTarget(target, data.CanonicalTargetsRole)
 | 
			
		||||
| 
						 | 
				
			
			@ -153,7 +132,7 @@ func trustedPush(ctx context.Context, cli *command.DockerCli, repoInfo *registry
 | 
			
		|||
		// already initialized and we have successfully downloaded the latest metadata
 | 
			
		||||
		err = addTargetToAllSignableRoles(repo, target)
 | 
			
		||||
	default:
 | 
			
		||||
		return notaryError(repoInfo.FullName(), err)
 | 
			
		||||
		return trust.NotaryError(repoInfo.FullName(), err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err == nil {
 | 
			
		||||
| 
						 | 
				
			
			@ -162,7 +141,7 @@ func trustedPush(ctx context.Context, cli *command.DockerCli, repoInfo *registry
 | 
			
		|||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Fprintf(cli.Out(), "Failed to sign %q:%s - %s\n", repoInfo.FullName(), tag, err.Error())
 | 
			
		||||
		return notaryError(repoInfo.FullName(), err)
 | 
			
		||||
		return trust.NotaryError(repoInfo.FullName(), err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Fprintf(cli.Out(), "Successfully signed %q:%s\n", repoInfo.FullName(), tag)
 | 
			
		||||
| 
						 | 
				
			
			@ -235,7 +214,7 @@ func imagePushPrivileged(ctx context.Context, cli *command.DockerCli, authConfig
 | 
			
		|||
func trustedPull(ctx context.Context, cli *command.DockerCli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error {
 | 
			
		||||
	var refs []target
 | 
			
		||||
 | 
			
		||||
	notaryRepo, err := GetNotaryRepository(cli, repoInfo, authConfig, "pull")
 | 
			
		||||
	notaryRepo, err := trust.GetNotaryRepository(cli, repoInfo, authConfig, "pull")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Fprintf(cli.Out(), "Error establishing connection to trust repository: %s\n", err)
 | 
			
		||||
		return err
 | 
			
		||||
| 
						 | 
				
			
			@ -243,9 +222,9 @@ func trustedPull(ctx context.Context, cli *command.DockerCli, repoInfo *registry
 | 
			
		|||
 | 
			
		||||
	if tagged, isTagged := ref.(reference.NamedTagged); !isTagged {
 | 
			
		||||
		// List all targets
 | 
			
		||||
		targets, err := notaryRepo.ListTargets(releasesRole, data.CanonicalTargetsRole)
 | 
			
		||||
		targets, err := notaryRepo.ListTargets(trust.ReleasesRole, data.CanonicalTargetsRole)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return notaryError(repoInfo.FullName(), err)
 | 
			
		||||
			return trust.NotaryError(repoInfo.FullName(), err)
 | 
			
		||||
		}
 | 
			
		||||
		for _, tgt := range targets {
 | 
			
		||||
			t, err := convertTarget(tgt.Target)
 | 
			
		||||
| 
						 | 
				
			
			@ -255,23 +234,23 @@ func trustedPull(ctx context.Context, cli *command.DockerCli, repoInfo *registry
 | 
			
		|||
			}
 | 
			
		||||
			// Only list tags in the top level targets role or the releases delegation role - ignore
 | 
			
		||||
			// all other delegation roles
 | 
			
		||||
			if tgt.Role != releasesRole && tgt.Role != data.CanonicalTargetsRole {
 | 
			
		||||
			if tgt.Role != trust.ReleasesRole && tgt.Role != data.CanonicalTargetsRole {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			refs = append(refs, t)
 | 
			
		||||
		}
 | 
			
		||||
		if len(refs) == 0 {
 | 
			
		||||
			return notaryError(repoInfo.FullName(), fmt.Errorf("No trusted tags for %s", repoInfo.FullName()))
 | 
			
		||||
			return trust.NotaryError(repoInfo.FullName(), fmt.Errorf("No trusted tags for %s", repoInfo.FullName()))
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		t, err := notaryRepo.GetTargetByName(tagged.Tag(), releasesRole, data.CanonicalTargetsRole)
 | 
			
		||||
		t, err := notaryRepo.GetTargetByName(tagged.Tag(), trust.ReleasesRole, data.CanonicalTargetsRole)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return notaryError(repoInfo.FullName(), err)
 | 
			
		||||
			return trust.NotaryError(repoInfo.FullName(), err)
 | 
			
		||||
		}
 | 
			
		||||
		// Only get the tag if it's in the top level targets role or the releases delegation role
 | 
			
		||||
		// ignore it if it's in any other delegation roles
 | 
			
		||||
		if t.Role != releasesRole && t.Role != data.CanonicalTargetsRole {
 | 
			
		||||
			return notaryError(repoInfo.FullName(), fmt.Errorf("No trust data for %s", tagged.Tag()))
 | 
			
		||||
		if t.Role != trust.ReleasesRole && t.Role != data.CanonicalTargetsRole {
 | 
			
		||||
			return trust.NotaryError(repoInfo.FullName(), fmt.Errorf("No trust data for %s", tagged.Tag()))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		logrus.Debugf("retrieving target for %s role\n", t.Role)
 | 
			
		||||
| 
						 | 
				
			
			@ -335,159 +314,6 @@ func imagePullPrivileged(ctx context.Context, cli *command.DockerCli, authConfig
 | 
			
		|||
	return jsonmessage.DisplayJSONMessagesToStream(responseBody, cli.Out(), nil)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func trustDirectory() string {
 | 
			
		||||
	return filepath.Join(cliconfig.ConfigDir(), "trust")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// certificateDirectory returns the directory containing
 | 
			
		||||
// TLS certificates for the given server. An error is
 | 
			
		||||
// returned if there was an error parsing the server string.
 | 
			
		||||
func certificateDirectory(server string) (string, error) {
 | 
			
		||||
	u, err := url.Parse(server)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return filepath.Join(cliconfig.ConfigDir(), "tls", u.Host), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func trustServer(index *registrytypes.IndexInfo) (string, error) {
 | 
			
		||||
	if s := os.Getenv("DOCKER_CONTENT_TRUST_SERVER"); s != "" {
 | 
			
		||||
		urlObj, err := url.Parse(s)
 | 
			
		||||
		if err != nil || urlObj.Scheme != "https" {
 | 
			
		||||
			return "", fmt.Errorf("valid https URL required for trust server, got %s", s)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return s, nil
 | 
			
		||||
	}
 | 
			
		||||
	if index.Official {
 | 
			
		||||
		return registry.NotaryServer, nil
 | 
			
		||||
	}
 | 
			
		||||
	return "https://" + index.Name, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type simpleCredentialStore struct {
 | 
			
		||||
	auth types.AuthConfig
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (scs simpleCredentialStore) Basic(u *url.URL) (string, string) {
 | 
			
		||||
	return scs.auth.Username, scs.auth.Password
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (scs simpleCredentialStore) RefreshToken(u *url.URL, service string) string {
 | 
			
		||||
	return scs.auth.IdentityToken
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (scs simpleCredentialStore) SetRefreshToken(*url.URL, string, string) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetNotaryRepository returns a NotaryRepository which stores all the
 | 
			
		||||
// information needed to operate on a notary repository.
 | 
			
		||||
// It creates an HTTP transport providing authentication support.
 | 
			
		||||
// TODO: move this too
 | 
			
		||||
func GetNotaryRepository(streams command.Streams, repoInfo *registry.RepositoryInfo, authConfig types.AuthConfig, actions ...string) (*client.NotaryRepository, error) {
 | 
			
		||||
	server, err := trustServer(repoInfo.Index)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var cfg = tlsconfig.ClientDefault()
 | 
			
		||||
	cfg.InsecureSkipVerify = !repoInfo.Index.Secure
 | 
			
		||||
 | 
			
		||||
	// Get certificate base directory
 | 
			
		||||
	certDir, err := certificateDirectory(server)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	logrus.Debugf("reading certificate directory: %s", certDir)
 | 
			
		||||
 | 
			
		||||
	if err := registry.ReadCertsDirectory(cfg, certDir); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	base := &http.Transport{
 | 
			
		||||
		Proxy: http.ProxyFromEnvironment,
 | 
			
		||||
		Dial: (&net.Dialer{
 | 
			
		||||
			Timeout:   30 * time.Second,
 | 
			
		||||
			KeepAlive: 30 * time.Second,
 | 
			
		||||
			DualStack: true,
 | 
			
		||||
		}).Dial,
 | 
			
		||||
		TLSHandshakeTimeout: 10 * time.Second,
 | 
			
		||||
		TLSClientConfig:     cfg,
 | 
			
		||||
		DisableKeepAlives:   true,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Skip configuration headers since request is not going to Docker daemon
 | 
			
		||||
	modifiers := registry.DockerHeaders(command.UserAgent(), http.Header{})
 | 
			
		||||
	authTransport := transport.NewTransport(base, modifiers...)
 | 
			
		||||
	pingClient := &http.Client{
 | 
			
		||||
		Transport: authTransport,
 | 
			
		||||
		Timeout:   5 * time.Second,
 | 
			
		||||
	}
 | 
			
		||||
	endpointStr := server + "/v2/"
 | 
			
		||||
	req, err := http.NewRequest("GET", endpointStr, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	challengeManager := challenge.NewSimpleManager()
 | 
			
		||||
 | 
			
		||||
	resp, err := pingClient.Do(req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		// Ignore error on ping to operate in offline mode
 | 
			
		||||
		logrus.Debugf("Error pinging notary server %q: %s", endpointStr, err)
 | 
			
		||||
	} else {
 | 
			
		||||
		defer resp.Body.Close()
 | 
			
		||||
 | 
			
		||||
		// Add response to the challenge manager to parse out
 | 
			
		||||
		// authentication header and register authentication method
 | 
			
		||||
		if err := challengeManager.AddResponse(resp); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	creds := simpleCredentialStore{auth: authConfig}
 | 
			
		||||
	tokenHandler := auth.NewTokenHandler(authTransport, creds, repoInfo.FullName(), actions...)
 | 
			
		||||
	basicHandler := auth.NewBasicHandler(creds)
 | 
			
		||||
	modifiers = append(modifiers, transport.RequestModifier(auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)))
 | 
			
		||||
	tr := transport.NewTransport(base, modifiers...)
 | 
			
		||||
 | 
			
		||||
	return client.NewNotaryRepository(
 | 
			
		||||
		trustDirectory(),
 | 
			
		||||
		repoInfo.FullName(),
 | 
			
		||||
		server,
 | 
			
		||||
		tr,
 | 
			
		||||
		getPassphraseRetriever(streams),
 | 
			
		||||
		trustpinning.TrustPinConfig{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getPassphraseRetriever(streams command.Streams) notary.PassRetriever {
 | 
			
		||||
	aliasMap := map[string]string{
 | 
			
		||||
		"root":     "root",
 | 
			
		||||
		"snapshot": "repository",
 | 
			
		||||
		"targets":  "repository",
 | 
			
		||||
		"default":  "repository",
 | 
			
		||||
	}
 | 
			
		||||
	baseRetriever := passphrase.PromptRetrieverWithInOut(streams.In(), streams.Out(), aliasMap)
 | 
			
		||||
	env := map[string]string{
 | 
			
		||||
		"root":     os.Getenv("DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE"),
 | 
			
		||||
		"snapshot": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
 | 
			
		||||
		"targets":  os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
 | 
			
		||||
		"default":  os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return func(keyName string, alias string, createNew bool, numAttempts int) (string, bool, error) {
 | 
			
		||||
		if v := env[alias]; v != "" {
 | 
			
		||||
			return v, numAttempts > 1, nil
 | 
			
		||||
		}
 | 
			
		||||
		// For non-root roles, we can also try the "default" alias if it is specified
 | 
			
		||||
		if v := env["default"]; v != "" && alias != data.CanonicalRootRole {
 | 
			
		||||
			return v, numAttempts > 1, nil
 | 
			
		||||
		}
 | 
			
		||||
		return baseRetriever(keyName, alias, createNew, numAttempts)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TrustedReference returns the canonical trusted reference for an image reference
 | 
			
		||||
func TrustedReference(ctx context.Context, cli *command.DockerCli, ref reference.NamedTagged) (reference.Canonical, error) {
 | 
			
		||||
	repoInfo, err := registry.ParseRepositoryInfo(ref)
 | 
			
		||||
| 
						 | 
				
			
			@ -498,20 +324,20 @@ func TrustedReference(ctx context.Context, cli *command.DockerCli, ref reference
 | 
			
		|||
	// Resolve the Auth config relevant for this server
 | 
			
		||||
	authConfig := command.ResolveAuthConfig(ctx, cli, repoInfo.Index)
 | 
			
		||||
 | 
			
		||||
	notaryRepo, err := GetNotaryRepository(cli, repoInfo, authConfig, "pull")
 | 
			
		||||
	notaryRepo, err := trust.GetNotaryRepository(cli, repoInfo, authConfig, "pull")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Fprintf(cli.Out(), "Error establishing connection to trust repository: %s\n", err)
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t, err := notaryRepo.GetTargetByName(ref.Tag(), releasesRole, data.CanonicalTargetsRole)
 | 
			
		||||
	t, err := notaryRepo.GetTargetByName(ref.Tag(), trust.ReleasesRole, data.CanonicalTargetsRole)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	// Only list tags in the top level targets role or the releases delegation role - ignore
 | 
			
		||||
	// all other delegation roles
 | 
			
		||||
	if t.Role != releasesRole && t.Role != data.CanonicalTargetsRole {
 | 
			
		||||
		return nil, notaryError(repoInfo.FullName(), fmt.Errorf("No trust data for %s", ref.Tag()))
 | 
			
		||||
	if t.Role != trust.ReleasesRole && t.Role != data.CanonicalTargetsRole {
 | 
			
		||||
		return nil, trust.NotaryError(repoInfo.FullName(), fmt.Errorf("No trust data for %s", ref.Tag()))
 | 
			
		||||
	}
 | 
			
		||||
	r, err := convertTarget(t.Target)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
| 
						 | 
				
			
			@ -540,34 +366,3 @@ func TagTrusted(ctx context.Context, cli *command.DockerCli, trustedRef referenc
 | 
			
		|||
 | 
			
		||||
	return cli.Client().ImageTag(ctx, trustedRef.String(), ref.String())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// notaryError formats an error message received from the notary service
 | 
			
		||||
func notaryError(repoName string, err error) error {
 | 
			
		||||
	switch err.(type) {
 | 
			
		||||
	case *json.SyntaxError:
 | 
			
		||||
		logrus.Debugf("Notary syntax error: %s", err)
 | 
			
		||||
		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)
 | 
			
		||||
	case signed.ErrExpired:
 | 
			
		||||
		return fmt.Errorf("Error: remote repository %s out-of-date: %v", repoName, err)
 | 
			
		||||
	case trustmanager.ErrKeyNotFound:
 | 
			
		||||
		return fmt.Errorf("Error: signing keys for remote repository %s not found: %v", repoName, err)
 | 
			
		||||
	case storage.NetworkError:
 | 
			
		||||
		return fmt.Errorf("Error: error contacting notary server: %v", err)
 | 
			
		||||
	case storage.ErrMetaNotFound:
 | 
			
		||||
		return fmt.Errorf("Error: trust data missing for remote repository %s or remote repository not found: %v", repoName, err)
 | 
			
		||||
	case trustpinning.ErrRootRotationFail, trustpinning.ErrValidationFail, signed.ErrInvalidKeyType:
 | 
			
		||||
		return fmt.Errorf("Warning: potential malicious behavior - trust data mismatch for remote repository %s: %v", repoName, err)
 | 
			
		||||
	case signed.ErrNoKeys:
 | 
			
		||||
		return fmt.Errorf("Error: could not find signing keys for remote repository %s, or could not decrypt signing key: %v", repoName, err)
 | 
			
		||||
	case signed.ErrLowVersion:
 | 
			
		||||
		return fmt.Errorf("Warning: potential malicious behavior - trust data version is lower than expected for remote repository %s: %v", repoName, err)
 | 
			
		||||
	case signed.ErrRoleThreshold:
 | 
			
		||||
		return fmt.Errorf("Warning: potential malicious behavior - trust data has insufficient signatures for remote repository %s: %v", repoName, err)
 | 
			
		||||
	case client.ErrRepositoryNotExist:
 | 
			
		||||
		return fmt.Errorf("Error: remote trust data does not exist for %s: %v", repoName, err)
 | 
			
		||||
	case signed.ErrInsufficientSignatures:
 | 
			
		||||
		return fmt.Errorf("Error: could not produce valid signature for %s.  If Yubikey was used, was touch input provided?: %v", repoName, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,7 @@ import (
 | 
			
		|||
	"testing"
 | 
			
		||||
 | 
			
		||||
	registrytypes "github.com/docker/docker/api/types/registry"
 | 
			
		||||
	"github.com/docker/docker/cli/trust"
 | 
			
		||||
	"github.com/docker/docker/registry"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -19,7 +20,7 @@ func TestENVTrustServer(t *testing.T) {
 | 
			
		|||
	if err := os.Setenv("DOCKER_CONTENT_TRUST_SERVER", "https://notary-test.com:5000"); err != nil {
 | 
			
		||||
		t.Fatal("Failed to set ENV variable")
 | 
			
		||||
	}
 | 
			
		||||
	output, err := trustServer(indexInfo)
 | 
			
		||||
	output, err := trust.Server(indexInfo)
 | 
			
		||||
	expectedStr := "https://notary-test.com:5000"
 | 
			
		||||
	if err != nil || output != expectedStr {
 | 
			
		||||
		t.Fatalf("Expected server to be %s, got %s", expectedStr, output)
 | 
			
		||||
| 
						 | 
				
			
			@ -32,7 +33,7 @@ func TestHTTPENVTrustServer(t *testing.T) {
 | 
			
		|||
	if err := os.Setenv("DOCKER_CONTENT_TRUST_SERVER", "http://notary-test.com:5000"); err != nil {
 | 
			
		||||
		t.Fatal("Failed to set ENV variable")
 | 
			
		||||
	}
 | 
			
		||||
	_, err := trustServer(indexInfo)
 | 
			
		||||
	_, err := trust.Server(indexInfo)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.Fatal("Expected error with invalid scheme")
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -40,7 +41,7 @@ func TestHTTPENVTrustServer(t *testing.T) {
 | 
			
		|||
 | 
			
		||||
func TestOfficialTrustServer(t *testing.T) {
 | 
			
		||||
	indexInfo := ®istrytypes.IndexInfo{Name: "testserver", Official: true}
 | 
			
		||||
	output, err := trustServer(indexInfo)
 | 
			
		||||
	output, err := trust.Server(indexInfo)
 | 
			
		||||
	if err != nil || output != registry.NotaryServer {
 | 
			
		||||
		t.Fatalf("Expected server to be %s, got %s", registry.NotaryServer, output)
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -48,7 +49,7 @@ func TestOfficialTrustServer(t *testing.T) {
 | 
			
		|||
 | 
			
		||||
func TestNonOfficialTrustServer(t *testing.T) {
 | 
			
		||||
	indexInfo := ®istrytypes.IndexInfo{Name: "testserver", Official: false}
 | 
			
		||||
	output, err := trustServer(indexInfo)
 | 
			
		||||
	output, err := trust.Server(indexInfo)
 | 
			
		||||
	expectedStr := "https://" + indexInfo.Name
 | 
			
		||||
	if err != nil || output != expectedStr {
 | 
			
		||||
		t.Fatalf("Expected server to be %s, got %s", expectedStr, output)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										221
									
								
								cli/trust/trust.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										221
									
								
								cli/trust/trust.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,221 @@
 | 
			
		|||
package trust
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/docker/distribution/registry/client/auth"
 | 
			
		||||
	"github.com/docker/distribution/registry/client/auth/challenge"
 | 
			
		||||
	"github.com/docker/distribution/registry/client/transport"
 | 
			
		||||
	"github.com/docker/docker/api/types"
 | 
			
		||||
	registrytypes "github.com/docker/docker/api/types/registry"
 | 
			
		||||
	"github.com/docker/docker/cli/command"
 | 
			
		||||
	"github.com/docker/docker/cliconfig"
 | 
			
		||||
	"github.com/docker/docker/registry"
 | 
			
		||||
	"github.com/docker/go-connections/tlsconfig"
 | 
			
		||||
	"github.com/docker/notary"
 | 
			
		||||
	"github.com/docker/notary/client"
 | 
			
		||||
	"github.com/docker/notary/passphrase"
 | 
			
		||||
	"github.com/docker/notary/storage"
 | 
			
		||||
	"github.com/docker/notary/trustmanager"
 | 
			
		||||
	"github.com/docker/notary/trustpinning"
 | 
			
		||||
	"github.com/docker/notary/tuf/data"
 | 
			
		||||
	"github.com/docker/notary/tuf/signed"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	// ReleasesRole is the role named "releases"
 | 
			
		||||
	ReleasesRole = path.Join(data.CanonicalTargetsRole, "releases")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func trustDirectory() string {
 | 
			
		||||
	return filepath.Join(cliconfig.ConfigDir(), "trust")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// certificateDirectory returns the directory containing
 | 
			
		||||
// TLS certificates for the given server. An error is
 | 
			
		||||
// returned if there was an error parsing the server string.
 | 
			
		||||
func certificateDirectory(server string) (string, error) {
 | 
			
		||||
	u, err := url.Parse(server)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return filepath.Join(cliconfig.ConfigDir(), "tls", u.Host), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Server returns the base URL for the trust server.
 | 
			
		||||
func Server(index *registrytypes.IndexInfo) (string, error) {
 | 
			
		||||
	if s := os.Getenv("DOCKER_CONTENT_TRUST_SERVER"); s != "" {
 | 
			
		||||
		urlObj, err := url.Parse(s)
 | 
			
		||||
		if err != nil || urlObj.Scheme != "https" {
 | 
			
		||||
			return "", fmt.Errorf("valid https URL required for trust server, got %s", s)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return s, nil
 | 
			
		||||
	}
 | 
			
		||||
	if index.Official {
 | 
			
		||||
		return registry.NotaryServer, nil
 | 
			
		||||
	}
 | 
			
		||||
	return "https://" + index.Name, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type simpleCredentialStore struct {
 | 
			
		||||
	auth types.AuthConfig
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (scs simpleCredentialStore) Basic(u *url.URL) (string, string) {
 | 
			
		||||
	return scs.auth.Username, scs.auth.Password
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (scs simpleCredentialStore) RefreshToken(u *url.URL, service string) string {
 | 
			
		||||
	return scs.auth.IdentityToken
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (scs simpleCredentialStore) SetRefreshToken(*url.URL, string, string) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetNotaryRepository returns a NotaryRepository which stores all the
 | 
			
		||||
// information needed to operate on a notary repository.
 | 
			
		||||
// It creates an HTTP transport providing authentication support.
 | 
			
		||||
func GetNotaryRepository(streams command.Streams, repoInfo *registry.RepositoryInfo, authConfig types.AuthConfig, actions ...string) (*client.NotaryRepository, error) {
 | 
			
		||||
	server, err := Server(repoInfo.Index)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var cfg = tlsconfig.ClientDefault()
 | 
			
		||||
	cfg.InsecureSkipVerify = !repoInfo.Index.Secure
 | 
			
		||||
 | 
			
		||||
	// Get certificate base directory
 | 
			
		||||
	certDir, err := certificateDirectory(server)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	logrus.Debugf("reading certificate directory: %s", certDir)
 | 
			
		||||
 | 
			
		||||
	if err := registry.ReadCertsDirectory(cfg, certDir); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	base := &http.Transport{
 | 
			
		||||
		Proxy: http.ProxyFromEnvironment,
 | 
			
		||||
		Dial: (&net.Dialer{
 | 
			
		||||
			Timeout:   30 * time.Second,
 | 
			
		||||
			KeepAlive: 30 * time.Second,
 | 
			
		||||
			DualStack: true,
 | 
			
		||||
		}).Dial,
 | 
			
		||||
		TLSHandshakeTimeout: 10 * time.Second,
 | 
			
		||||
		TLSClientConfig:     cfg,
 | 
			
		||||
		DisableKeepAlives:   true,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Skip configuration headers since request is not going to Docker daemon
 | 
			
		||||
	modifiers := registry.DockerHeaders(command.UserAgent(), http.Header{})
 | 
			
		||||
	authTransport := transport.NewTransport(base, modifiers...)
 | 
			
		||||
	pingClient := &http.Client{
 | 
			
		||||
		Transport: authTransport,
 | 
			
		||||
		Timeout:   5 * time.Second,
 | 
			
		||||
	}
 | 
			
		||||
	endpointStr := server + "/v2/"
 | 
			
		||||
	req, err := http.NewRequest("GET", endpointStr, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	challengeManager := challenge.NewSimpleManager()
 | 
			
		||||
 | 
			
		||||
	resp, err := pingClient.Do(req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		// Ignore error on ping to operate in offline mode
 | 
			
		||||
		logrus.Debugf("Error pinging notary server %q: %s", endpointStr, err)
 | 
			
		||||
	} else {
 | 
			
		||||
		defer resp.Body.Close()
 | 
			
		||||
 | 
			
		||||
		// Add response to the challenge manager to parse out
 | 
			
		||||
		// authentication header and register authentication method
 | 
			
		||||
		if err := challengeManager.AddResponse(resp); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	creds := simpleCredentialStore{auth: authConfig}
 | 
			
		||||
	tokenHandler := auth.NewTokenHandler(authTransport, creds, repoInfo.FullName(), actions...)
 | 
			
		||||
	basicHandler := auth.NewBasicHandler(creds)
 | 
			
		||||
	modifiers = append(modifiers, transport.RequestModifier(auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)))
 | 
			
		||||
	tr := transport.NewTransport(base, modifiers...)
 | 
			
		||||
 | 
			
		||||
	return client.NewNotaryRepository(
 | 
			
		||||
		trustDirectory(),
 | 
			
		||||
		repoInfo.FullName(),
 | 
			
		||||
		server,
 | 
			
		||||
		tr,
 | 
			
		||||
		getPassphraseRetriever(streams),
 | 
			
		||||
		trustpinning.TrustPinConfig{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getPassphraseRetriever(streams command.Streams) notary.PassRetriever {
 | 
			
		||||
	aliasMap := map[string]string{
 | 
			
		||||
		"root":     "root",
 | 
			
		||||
		"snapshot": "repository",
 | 
			
		||||
		"targets":  "repository",
 | 
			
		||||
		"default":  "repository",
 | 
			
		||||
	}
 | 
			
		||||
	baseRetriever := passphrase.PromptRetrieverWithInOut(streams.In(), streams.Out(), aliasMap)
 | 
			
		||||
	env := map[string]string{
 | 
			
		||||
		"root":     os.Getenv("DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE"),
 | 
			
		||||
		"snapshot": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
 | 
			
		||||
		"targets":  os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
 | 
			
		||||
		"default":  os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return func(keyName string, alias string, createNew bool, numAttempts int) (string, bool, error) {
 | 
			
		||||
		if v := env[alias]; v != "" {
 | 
			
		||||
			return v, numAttempts > 1, nil
 | 
			
		||||
		}
 | 
			
		||||
		// For non-root roles, we can also try the "default" alias if it is specified
 | 
			
		||||
		if v := env["default"]; v != "" && alias != data.CanonicalRootRole {
 | 
			
		||||
			return v, numAttempts > 1, nil
 | 
			
		||||
		}
 | 
			
		||||
		return baseRetriever(keyName, alias, createNew, numAttempts)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NotaryError formats an error message received from the notary service
 | 
			
		||||
func NotaryError(repoName string, err error) error {
 | 
			
		||||
	switch err.(type) {
 | 
			
		||||
	case *json.SyntaxError:
 | 
			
		||||
		logrus.Debugf("Notary syntax error: %s", err)
 | 
			
		||||
		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)
 | 
			
		||||
	case signed.ErrExpired:
 | 
			
		||||
		return fmt.Errorf("Error: remote repository %s out-of-date: %v", repoName, err)
 | 
			
		||||
	case trustmanager.ErrKeyNotFound:
 | 
			
		||||
		return fmt.Errorf("Error: signing keys for remote repository %s not found: %v", repoName, err)
 | 
			
		||||
	case storage.NetworkError:
 | 
			
		||||
		return fmt.Errorf("Error: error contacting notary server: %v", err)
 | 
			
		||||
	case storage.ErrMetaNotFound:
 | 
			
		||||
		return fmt.Errorf("Error: trust data missing for remote repository %s or remote repository not found: %v", repoName, err)
 | 
			
		||||
	case trustpinning.ErrRootRotationFail, trustpinning.ErrValidationFail, signed.ErrInvalidKeyType:
 | 
			
		||||
		return fmt.Errorf("Warning: potential malicious behavior - trust data mismatch for remote repository %s: %v", repoName, err)
 | 
			
		||||
	case signed.ErrNoKeys:
 | 
			
		||||
		return fmt.Errorf("Error: could not find signing keys for remote repository %s, or could not decrypt signing key: %v", repoName, err)
 | 
			
		||||
	case signed.ErrLowVersion:
 | 
			
		||||
		return fmt.Errorf("Warning: potential malicious behavior - trust data version is lower than expected for remote repository %s: %v", repoName, err)
 | 
			
		||||
	case signed.ErrRoleThreshold:
 | 
			
		||||
		return fmt.Errorf("Warning: potential malicious behavior - trust data has insufficient signatures for remote repository %s: %v", repoName, err)
 | 
			
		||||
	case client.ErrRepositoryNotExist:
 | 
			
		||||
		return fmt.Errorf("Error: remote trust data does not exist for %s: %v", repoName, err)
 | 
			
		||||
	case signed.ErrInsufficientSignatures:
 | 
			
		||||
		return fmt.Errorf("Error: could not produce valid signature for %s.  If Yubikey was used, was touch input provided?: %v", repoName, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue