github.com/jcarley/cli@v0.0.0-20180201210820-966d90434c30/lib/images/tuf.go (about)

     1  package images
     2  
     3  import (
     4  	"context"
     5  	"encoding/base64"
     6  	"encoding/hex"
     7  	"encoding/json"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"net"
    11  	"net/http"
    12  	"net/url"
    13  	"os"
    14  	"path"
    15  	"runtime"
    16  	"strings"
    17  	"time"
    18  
    19  	"github.com/Sirupsen/logrus"
    20  	"github.com/daticahealth/cli/lib/prompts"
    21  	"github.com/daticahealth/cli/models"
    22  	"github.com/docker/distribution/registry/client/auth"
    23  	"github.com/docker/distribution/registry/client/auth/challenge"
    24  	"github.com/docker/distribution/registry/client/transport"
    25  	"github.com/docker/docker/api/types"
    26  	"github.com/docker/docker/api/types/filters"
    27  	dockerClient "github.com/docker/docker/client"
    28  	"github.com/docker/docker/pkg/jsonmessage"
    29  	"github.com/docker/go-connections/tlsconfig"
    30  	"github.com/docker/notary"
    31  	notaryClient "github.com/docker/notary/client"
    32  	"github.com/docker/notary/client/changelist"
    33  	"github.com/docker/notary/cryptoservice"
    34  	"github.com/docker/notary/passphrase"
    35  	"github.com/docker/notary/trustmanager"
    36  	"github.com/docker/notary/trustpinning"
    37  	"github.com/docker/notary/tuf/data"
    38  	"github.com/olekukonko/tablewriter"
    39  	digest "github.com/opencontainers/go-digest"
    40  )
    41  
    42  var registries = map[string]string{
    43  	"sbox05":  "registry-sbox05.datica.com",
    44  	"default": "registry.datica.com",
    45  }
    46  
    47  var notaryServers = map[string]string{
    48  	"sbox05":  "https://notary-sandbox.datica.com",
    49  	"default": "https://notary.datica.com",
    50  }
    51  
    52  // Errors for image handling
    53  const (
    54  	InvalidImageName             = "Invalid image name"
    55  	IncorrectNamespace           = "Incorrect namespace for your environment"
    56  	IncorrectRegistryOrNamespace = "Incorrect registry or namespace for your environment"
    57  	MissingTrustData             = "does not have trust data for"
    58  	ImageDoesNotExist            = "No such image"
    59  	CancelingPush                = "Canceling push request"
    60  
    61  	CanonicalTargetsRole = "targets"
    62  )
    63  
    64  // Constants for image handling
    65  const (
    66  	DefaultTag = "latest"
    67  	trustPath  = ".docker/trust"
    68  )
    69  
    70  // Target contains metadata about a target
    71  type Target struct {
    72  	Name   string
    73  	Digest digest.Digest
    74  	Size   int64
    75  	Role   string
    76  }
    77  
    78  // AuxData contains metadata about the content that was pushed
    79  type AuxData struct {
    80  	Tag    string `json:"Tag"`
    81  	Digest string `json:"Digest"`
    82  	Size   int64  `json:"Size"`
    83  }
    84  
    85  // Push parses a given name into registry/namespace/image:tag and attempts to push it to the remote registry
    86  func (d *SImages) Push(name string, user *models.User, env *models.Environment, ip prompts.IPrompts) (*models.Image, error) {
    87  	ctx := context.Background()
    88  	dockerCli, err := dockerClient.NewEnvClient()
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  	defer dockerCli.Close()
    93  	dockerCli.NegotiateAPIVersion(ctx)
    94  
    95  	repositoryName, tag, err := d.GetGloballyUniqueNamespace(name, env, true)
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  
   100  	if tag == "" {
   101  		tag = DefaultTag
   102  	}
   103  	fullImageName := strings.Join([]string{repositoryName, tag}, ":")
   104  	if fullImageName != name {
   105  		if err = dockerCli.ImageTag(ctx, name, fullImageName); err != nil {
   106  			if !strings.Contains(err.Error(), ImageDoesNotExist) {
   107  				return nil, err
   108  			}
   109  
   110  			// Check if the fully formatted repo name exists, and ask if user wants to push that instead
   111  			if !localImageExists(ctx, fullImageName, dockerCli) {
   112  				return nil, err
   113  			} else if yesNo := ip.YesNo(err.Error(), fmt.Sprintf("Would you like to push %s instead? (y/n) ", fullImageName)); yesNo != nil {
   114  				return nil, fmt.Errorf(CancelingPush)
   115  			}
   116  		} else {
   117  			logrus.Printf("Pushing image %s to %s\n", name, fullImageName)
   118  		}
   119  	} else {
   120  		logrus.Printf("Pushing image %s", fullImageName)
   121  	}
   122  
   123  	resp, err := dockerCli.ImagePush(ctx, fullImageName, types.ImagePushOptions{RegistryAuth: dockerAuth(user)})
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  	defer resp.Close()
   128  
   129  	var digest models.ContentDigest
   130  	if err = jsonmessage.DisplayJSONMessagesStream(resp, os.Stdout, os.Stdout.Fd(), true,
   131  		func(aux *json.RawMessage) {
   132  			var auxData AuxData
   133  			if data, jsonErr := aux.MarshalJSON(); jsonErr == nil {
   134  				json.Unmarshal(data, &auxData)
   135  				hashParts := strings.Split(auxData.Digest, ":")
   136  				digest.HashType = hashParts[0]
   137  				digest.Hash = hashParts[1]
   138  				digest.Size = auxData.Size
   139  			}
   140  		}); err != nil {
   141  		return nil, err
   142  	}
   143  
   144  	return &models.Image{
   145  		Name:   repositoryName,
   146  		Tag:    tag,
   147  		Digest: &digest,
   148  	}, nil
   149  }
   150  
   151  // Pull parses a name into registry/namespace/image:tag and attempts to retrieve it from the remote registry
   152  func (d *SImages) Pull(name string, target *Target, user *models.User, env *models.Environment) error {
   153  	ctx := context.Background()
   154  	dockerCli, err := dockerClient.NewEnvClient()
   155  	if err != nil {
   156  		return err
   157  	}
   158  	defer dockerCli.Close()
   159  	dockerCli.NegotiateAPIVersion(ctx)
   160  
   161  	ref := strings.Join([]string{name, string(target.Digest)}, "@")
   162  	resp, err := dockerCli.ImagePull(ctx, ref, types.ImagePullOptions{RegistryAuth: dockerAuth(user)})
   163  	if err != nil {
   164  		return err
   165  	}
   166  	defer resp.Close()
   167  	return jsonmessage.DisplayJSONMessagesStream(resp, os.Stdout, os.Stdout.Fd(), true, nil)
   168  }
   169  
   170  // InitNotaryRepo intializes a notary repository
   171  func (d *SImages) InitNotaryRepo(repo notaryClient.Repository, rootKeyPath string) error {
   172  	rootTrustDir := fmt.Sprintf("%s/%s", userHomeDir(), trustPath)
   173  	if err := os.MkdirAll(rootTrustDir, 0700); err != nil {
   174  		return err
   175  	}
   176  
   177  	rootKeyIDs, err := getRootKey(rootKeyPath, repo, getPassphraseRetriever())
   178  	if err != nil {
   179  		return err
   180  	}
   181  	if err = repo.Initialize(rootKeyIDs); err != nil {
   182  		return err
   183  	}
   184  	return nil
   185  }
   186  
   187  // AddTargetHash adds the given content hash to a notary repo and sends a signing request to the server
   188  func (d *SImages) AddTargetHash(repo notaryClient.Repository, digest *models.ContentDigest, tag string, publish bool) error {
   189  	targetHash := data.Hashes{}
   190  	sha256, err := hex.DecodeString(digest.Hash)
   191  	if err != nil {
   192  		return err
   193  	}
   194  	targetHash[digest.HashType] = sha256
   195  
   196  	// var targetCustom *canonicalJson.RawMessage
   197  	target := &notaryClient.Target{Name: tag, Hashes: targetHash, Length: digest.Size}
   198  	if err = repo.AddTarget(target, data.CanonicalTargetsRole); err != nil {
   199  		return err
   200  	}
   201  	if publish {
   202  		return d.Publish(repo)
   203  	}
   204  	return nil
   205  }
   206  
   207  // ListTargets intializes a notary repository
   208  func (d *SImages) ListTargets(repo notaryClient.Repository, roles ...string) ([]*Target, error) {
   209  	targets, err := repo.ListTargets(data.NewRoleList(roles)...)
   210  	if err != nil {
   211  		return nil, err
   212  	}
   213  	var ts []*Target
   214  	for _, t := range targets {
   215  		t1, err := convertTarget(t)
   216  		if err != nil {
   217  			return nil, err
   218  		}
   219  		ts = append(ts, t1)
   220  	}
   221  	return ts, nil
   222  }
   223  
   224  // LookupTarget searches for a specific target in a repository by tag name
   225  func (d *SImages) LookupTarget(repo notaryClient.Repository, tag string) (*Target, error) {
   226  	target, err := repo.GetTargetByName(tag)
   227  	if err != nil {
   228  		return nil, err
   229  	}
   230  	role := string(target.Role)
   231  	if role != path.Join(CanonicalTargetsRole, "releases") && role != CanonicalTargetsRole {
   232  		return nil, fmt.Errorf("no canonical target found for %s", tag)
   233  	}
   234  	t, err := convertTarget(target)
   235  	if err != nil {
   236  		return nil, err
   237  	}
   238  	return t, nil
   239  }
   240  
   241  // DeleteTargets deletes the signed targets for a list of tags
   242  func (d *SImages) DeleteTargets(repo notaryClient.Repository, tags []string, publish bool) error {
   243  	//TODO: Check if a target is associated with a deployed release before allowing it to be unsigned
   244  	for _, tag := range tags {
   245  		if err := repo.RemoveTarget(tag, "targets"); err != nil {
   246  			return err
   247  		}
   248  	}
   249  
   250  	if publish {
   251  		return d.Publish(repo)
   252  	}
   253  	return nil
   254  }
   255  
   256  // PrintChangelist prints out the users unpublished changes in a formatted table
   257  func (d *SImages) PrintChangelist(changes []changelist.Change) {
   258  	data := [][]string{[]string{"#", "Action", "Scope", "Type", "Target"}, []string{"-", "------", "-----", "----", "------"}}
   259  	data = append(data)
   260  	for i, c := range changes {
   261  		data = append(data, []string{fmt.Sprintf("%d\n", i), c.Action(), c.Scope().String(), c.Type(), c.Path()})
   262  	}
   263  
   264  	logrus.Println()
   265  	table := tablewriter.NewWriter(logrus.StandardLogger().Out)
   266  	table.SetBorder(false)
   267  	table.SetRowLine(false)
   268  	table.SetHeaderLine(false)
   269  	table.SetAlignment(1)
   270  	table.SetCenterSeparator("")
   271  	table.SetColumnSeparator("")
   272  	table.SetRowSeparator("")
   273  	table.AppendBulk(data)
   274  	table.Render()
   275  	logrus.Println()
   276  }
   277  
   278  // CheckChangelist prompts the user if they have unpublished changes to let them clear
   279  //	undesired changes before publishing, or verify that the changes should be published
   280  func (d *SImages) CheckChangelist(repo notaryClient.Repository, ip prompts.IPrompts) error {
   281  	if changelist, err := repo.GetChangelist(); err == nil {
   282  		changes := changelist.List()
   283  		if len(changes) > 0 {
   284  			logrus.Println("The following unpublished changes were found in your local trust repository:")
   285  			d.PrintChangelist(changes)
   286  			publishWarning := "These changes will be published along with your current request."
   287  			if err = ip.YesNo(publishWarning, "Would you like to proceed? (y/n) "); err != nil {
   288  				logrus.Println("Use the `images targets reset <image>` command to clear undesired changes")
   289  				return err
   290  			}
   291  		}
   292  	}
   293  	return nil
   294  }
   295  
   296  // GetNotaryRepository returns a pointer to the notary repository for an image
   297  func (d *SImages) GetNotaryRepository(pod, imageName string, user *models.User) notaryClient.Repository {
   298  	notaryServer := getServer(pod, notaryServers)
   299  	transport, err := getTransport(imageName, notaryServer, user, readWrite)
   300  	if err != nil {
   301  		logrus.Fatalln(err)
   302  	}
   303  	rootTrustDir := fmt.Sprintf("%s/%s", userHomeDir(), trustPath)
   304  	repo, err := notaryClient.NewFileCachedRepository(
   305  		rootTrustDir,
   306  		data.GUN(imageName),
   307  		notaryServer,
   308  		transport,
   309  		getPassphraseRetriever(),
   310  		trustpinning.TrustPinConfig{},
   311  	)
   312  	if err != nil {
   313  		logrus.Fatalln(err)
   314  	}
   315  	return repo
   316  }
   317  
   318  // GetGloballyUniqueNamespace returns the fully formatted name for an image <registry>/<namespace>/<image> and a tag if present
   319  func (d *SImages) GetGloballyUniqueNamespace(name string, env *models.Environment, includeRegistry bool) (string, string, error) {
   320  	var repositoryName string
   321  	var image string
   322  	var tag string
   323  
   324  	imageParts := strings.Split(strings.TrimPrefix(name, "/"), ":")
   325  	switch len(imageParts) {
   326  	case 1:
   327  		image = imageParts[0]
   328  	case 2:
   329  		image = imageParts[0]
   330  		tag = imageParts[1]
   331  	default:
   332  		return "", "", fmt.Errorf(InvalidImageName)
   333  	}
   334  
   335  	repoParts := strings.Split(image, "/")
   336  	registry := getServer(env.Pod, registries)
   337  
   338  	var repo string
   339  	switch len(repoParts) {
   340  	case 1:
   341  		repo = repoParts[0]
   342  	case 2:
   343  		if repoParts[0] != env.Namespace {
   344  			if repoParts[0] != registry {
   345  				return "", "", fmt.Errorf(IncorrectNamespace)
   346  			}
   347  			//Allow users to pull public images
   348  			return image, tag, nil
   349  		}
   350  		repo = repoParts[1]
   351  	case 3:
   352  		if repoParts[0] != registry || repoParts[1] != env.Namespace {
   353  			return "", "", fmt.Errorf(IncorrectRegistryOrNamespace)
   354  		}
   355  		repo = repoParts[2]
   356  	default:
   357  		return "", "", fmt.Errorf(InvalidImageName)
   358  	}
   359  	if includeRegistry {
   360  		repositoryName = fmt.Sprintf("%s/%s/%s", registry, env.Namespace, repo)
   361  	} else {
   362  		repositoryName = fmt.Sprintf("%s/%s", env.Namespace, repo)
   363  	}
   364  	return repositoryName, tag, nil
   365  }
   366  
   367  // Publish publishes changes to a repo
   368  func (d *SImages) Publish(repo notaryClient.Repository) error {
   369  	if err := repo.Publish(); err != nil {
   370  		return err
   371  	}
   372  	return nil
   373  }
   374  
   375  // dockerAuth returns a sessionized auth string for registry and notary requests
   376  func dockerAuth(user *models.User) string {
   377  	authConfig := types.AuthConfig{
   378  		Username: user.Email,
   379  		Password: fmt.Sprintf("SessionToken=%s", user.SessionToken),
   380  	}
   381  	encodedJSON, err := json.Marshal(authConfig)
   382  	if err != nil {
   383  		logrus.Fatal(err)
   384  	}
   385  	return base64.URLEncoding.EncodeToString(encodedJSON)
   386  }
   387  
   388  func localImageExists(ctx context.Context, fullImageName string, dockerCli *dockerClient.Client) bool {
   389  	imageList, err := dockerCli.ImageList(ctx, types.ImageListOptions{All: true, Filters: filters.NewArgs(filters.Arg("reference", fullImageName))})
   390  	if err != nil {
   391  		return false
   392  	}
   393  	return len(imageList) > 0
   394  }
   395  
   396  func getServer(pod string, serverMap map[string]string) string {
   397  	if server, ok := serverMap[pod]; ok {
   398  		return server
   399  	}
   400  	return serverMap["default"]
   401  }
   402  
   403  func getPassphraseRetriever() notary.PassRetriever {
   404  	baseRetriever := passphrase.PromptRetriever()
   405  	env := map[string]string{
   406  		"root":       os.Getenv("NOTARY_ROOT_PASSPHRASE"),
   407  		"targets":    os.Getenv("NOTARY_TARGETS_PASSPHRASE"),
   408  		"snapshot":   os.Getenv("NOTARY_SNAPSHOT_PASSPHRASE"),
   409  		"delegation": os.Getenv("NOTARY_DELEGATION_PASSPHRASE"),
   410  	}
   411  
   412  	return func(keyName string, alias string, createNew bool, numAttempts int) (string, bool, error) {
   413  		if v := env[alias]; v != "" {
   414  			return v, numAttempts > 1, nil
   415  		}
   416  		// For delegation roles, we can also try the "delegation" alias if it is specified
   417  		// Note that we don't check if the role name is for a delegation to allow for names like "user"
   418  		// since delegation keys can be shared across repositories
   419  		// This cannot be a base role or imported key, though.
   420  		if v := env["delegation"]; !data.IsBaseRole(data.RoleName(alias)) && v != "" {
   421  			return v, numAttempts > 1, nil
   422  		}
   423  		return baseRetriever(keyName, alias, createNew, numAttempts)
   424  	}
   425  }
   426  
   427  func getRootKey(rootKeyPath string, repo notaryClient.Repository, retriever notary.PassRetriever) ([]string, error) {
   428  	var rootKeyList []string
   429  	cryptoService := repo.GetCryptoService()
   430  	if rootKeyPath != "" {
   431  		privKey, err := readRootKey(rootKeyPath, retriever)
   432  		if err != nil {
   433  			return nil, err
   434  		}
   435  		err = cryptoService.AddKey(data.CanonicalRootRole, "", privKey)
   436  		if err != nil {
   437  			return nil, fmt.Errorf("Error importing key: %v", err)
   438  		}
   439  		rootKeyList = []string{privKey.ID()}
   440  	} else {
   441  		rootKeyList = cryptoService.ListKeys(data.CanonicalRootRole)
   442  	}
   443  
   444  	if len(rootKeyList) < 1 {
   445  		logrus.Println("No root keys found. Generating a new root key...")
   446  		rootPublicKey, err := cryptoService.Create(data.CanonicalRootRole, "", data.ECDSAKey)
   447  		if err != nil {
   448  			return nil, err
   449  		}
   450  		rootKeyList = []string{rootPublicKey.ID()}
   451  	} else {
   452  		// Chooses the first root key available, which is initialization specific
   453  		// but should return the HW one first.
   454  		logrus.Printf("Root key found, using: %s\n", rootKeyList[0])
   455  		rootKeyList = rootKeyList[0:1]
   456  	}
   457  
   458  	return rootKeyList, nil
   459  }
   460  
   461  // Attempt to read a role key from a file, and return it as a data.PrivateKey
   462  // Root key must be encrypted
   463  func readRootKey(rootKeyPath string, retriever notary.PassRetriever) (data.PrivateKey, error) {
   464  	keyFile, err := os.Open(rootKeyPath)
   465  	if err != nil {
   466  		return nil, fmt.Errorf("Opening file to import as a root key: %v", err)
   467  	}
   468  	defer keyFile.Close()
   469  
   470  	pemBytes, err := ioutil.ReadAll(keyFile)
   471  	if err != nil {
   472  		return nil, fmt.Errorf("Error reading input root key file: %v", err)
   473  	}
   474  	if err = cryptoservice.CheckRootKeyIsEncrypted(pemBytes); err != nil {
   475  		return nil, err
   476  	}
   477  
   478  	privKey, _, err := trustmanager.GetPasswdDecryptBytes(retriever, pemBytes, "", data.CanonicalRootRole.String())
   479  	if err != nil {
   480  		return nil, err
   481  	}
   482  
   483  	return privKey, nil
   484  }
   485  
   486  type httpAccess int
   487  
   488  const (
   489  	readOnly httpAccess = iota
   490  	readWrite
   491  	admin
   492  )
   493  
   494  func getTransport(gun, notaryServer string, user *models.User, permission httpAccess) (http.RoundTripper, error) {
   495  	tlsConfig, err := tlsconfig.Client(tlsconfig.Options{
   496  		CAFile:             "",
   497  		InsecureSkipVerify: false,
   498  		CertFile:           "",
   499  		KeyFile:            "",
   500  	})
   501  	if err != nil {
   502  		return nil, fmt.Errorf("unable to configure TLS: %s", err.Error())
   503  	}
   504  
   505  	base := &http.Transport{
   506  		Proxy: http.ProxyFromEnvironment,
   507  		Dial: (&net.Dialer{
   508  			Timeout:   30 * time.Second,
   509  			KeepAlive: 30 * time.Second,
   510  			DualStack: true,
   511  		}).Dial,
   512  		TLSHandshakeTimeout: 10 * time.Second,
   513  		TLSClientConfig:     tlsConfig,
   514  		DisableKeepAlives:   true,
   515  	}
   516  	return tokenAuth(notaryServer, base, gun, user, permission)
   517  }
   518  
   519  func tokenAuth(trustServerURL string, baseTransport *http.Transport, gun string, user *models.User, permission httpAccess) (http.RoundTripper, error) {
   520  	authTransport := transport.NewTransport(baseTransport)
   521  	pingClient := &http.Client{
   522  		Transport: authTransport,
   523  		Timeout:   5 * time.Second,
   524  	}
   525  	endpoint, err := url.Parse(trustServerURL)
   526  	if err != nil {
   527  		return nil, fmt.Errorf("Could not parse remote trust server url (%s): %s", trustServerURL, err.Error())
   528  	}
   529  	if endpoint.Scheme == "" {
   530  		return nil, fmt.Errorf("Trust server url has to be in the form of http(s)://URL:PORT. Got: %s", trustServerURL)
   531  	}
   532  	subPath, err := url.Parse("v2/")
   533  	if err != nil {
   534  		return nil, fmt.Errorf("Failed to parse v2 subpath. This error should not have been reached. Please report it as an issue at https://github.com/docker/notary/issues: %s", err.Error())
   535  	}
   536  	endpoint = endpoint.ResolveReference(subPath)
   537  	req, err := http.NewRequest("GET", endpoint.String(), nil)
   538  	if err != nil {
   539  		return nil, err
   540  	}
   541  	resp, err := pingClient.Do(req)
   542  	if err != nil {
   543  		logrus.Errorf("could not reach %s: %s", trustServerURL, err.Error())
   544  		logrus.Info("continuing in offline mode")
   545  		return nil, nil
   546  	}
   547  	// non-nil err means we must close body
   548  	defer resp.Body.Close()
   549  	if (resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices) &&
   550  		resp.StatusCode != http.StatusUnauthorized {
   551  		// If we didn't get a 2XX range or 401 status code, we're not talking to a notary server.
   552  		// The http client should be configured to handle redirects so at this point, 3XX is
   553  		// not a valid status code.
   554  		logrus.Errorf("could not reach %s: %d", trustServerURL, resp.StatusCode)
   555  		logrus.Info("continuing in offline mode")
   556  		return nil, nil
   557  	}
   558  
   559  	// challengeManager := auth.NewSimpleChallengeManager()
   560  	challengeManager := challenge.NewSimpleManager()
   561  	if err := challengeManager.AddResponse(resp); err != nil {
   562  		return nil, err
   563  	}
   564  
   565  	// ps := passwordStore{anonymous: permission == readOnly}
   566  	creds := credentials{
   567  		username:     user.Email,
   568  		sessionToken: user.SessionToken,
   569  	}
   570  
   571  	var actions []string
   572  	switch permission {
   573  	case admin:
   574  		actions = []string{"*"}
   575  	case readWrite:
   576  		actions = []string{"push", "pull"}
   577  	case readOnly:
   578  		actions = []string{"pull"}
   579  	default:
   580  		return nil, fmt.Errorf("Invalid permission requested for token authentication of gun %s", gun)
   581  	}
   582  
   583  	tokenHandler := auth.NewTokenHandler(authTransport, creds, gun, actions...)
   584  	basicHandler := auth.NewBasicHandler(creds)
   585  
   586  	modifier := auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)
   587  
   588  	if permission != readOnly {
   589  		return newAuthRoundTripper(transport.NewTransport(baseTransport, modifier)), nil
   590  	}
   591  
   592  	// Try to authenticate read only repositories using basic username/password authentication
   593  	return newAuthRoundTripper(transport.NewTransport(baseTransport, modifier),
   594  		transport.NewTransport(baseTransport, auth.NewAuthorizer(challengeManager, auth.NewTokenHandler(authTransport, creds, gun, actions...)))), nil
   595  }
   596  
   597  // authRoundTripper tries to authenticate the requests via multiple HTTP transactions (until first succeed)
   598  type authRoundTripper struct {
   599  	trippers []http.RoundTripper
   600  }
   601  
   602  func newAuthRoundTripper(trippers ...http.RoundTripper) http.RoundTripper {
   603  	return &authRoundTripper{trippers: trippers}
   604  }
   605  
   606  func (a *authRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
   607  	var resp *http.Response
   608  	// Try all run all transactions
   609  	for _, t := range a.trippers {
   610  		var err error
   611  		resp, err = t.RoundTrip(req)
   612  		// Reject on error
   613  		if err != nil {
   614  			return resp, err
   615  		}
   616  
   617  		// Stop when request is authorized/unknown error
   618  		if resp.StatusCode != http.StatusUnauthorized {
   619  			return resp, nil
   620  		}
   621  	}
   622  
   623  	// Return the last response
   624  	return resp, nil
   625  }
   626  
   627  type credentials struct {
   628  	username     string
   629  	sessionToken string
   630  	refreshToken string
   631  }
   632  
   633  func (c credentials) Basic(url *url.URL) (string, string) {
   634  	return c.username, fmt.Sprintf("SessionToken=%s", c.sessionToken)
   635  }
   636  
   637  func (c credentials) RefreshToken(url *url.URL, service string) string {
   638  	return c.refreshToken
   639  }
   640  
   641  func (c credentials) SetRefreshToken(realm *url.URL, service, token string) {
   642  	c.refreshToken = token
   643  }
   644  
   645  func userHomeDir() string {
   646  	env := "HOME"
   647  	if runtime.GOOS == "windows" {
   648  		env = "USERPROFILE"
   649  	} else if runtime.GOOS == "plan9" {
   650  		env = "home"
   651  	}
   652  	return os.Getenv(env)
   653  }
   654  
   655  func convertTarget(t *notaryClient.TargetWithRole) (*Target, error) {
   656  	h, ok := t.Hashes["sha256"]
   657  	if !ok {
   658  		return nil, fmt.Errorf("no valid hash, expecting sha256")
   659  	}
   660  	return &Target{
   661  		Name:   t.Name,
   662  		Digest: digest.NewDigestFromHex("sha256", hex.EncodeToString(h)),
   663  		Size:   t.Length,
   664  		Role:   string(t.Role),
   665  	}, nil
   666  }