github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/cmd/ctr/commands/resolver.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package commands
    18  
    19  import (
    20  	"bufio"
    21  	gocontext "context"
    22  	"crypto/tls"
    23  	"crypto/x509"
    24  	"fmt"
    25  	"io/ioutil"
    26  	"strings"
    27  
    28  	"github.com/containerd/console"
    29  	"github.com/containerd/containerd/remotes"
    30  	"github.com/containerd/containerd/remotes/docker"
    31  	"github.com/containerd/containerd/remotes/docker/config"
    32  	"github.com/pkg/errors"
    33  	"github.com/urfave/cli"
    34  )
    35  
    36  // PushTracker returns a new InMemoryTracker which tracks the ref status
    37  var PushTracker = docker.NewInMemoryTracker()
    38  
    39  func passwordPrompt() (string, error) {
    40  	c := console.Current()
    41  	defer c.Reset()
    42  
    43  	if err := c.DisableEcho(); err != nil {
    44  		return "", errors.Wrap(err, "failed to disable echo")
    45  	}
    46  
    47  	line, _, err := bufio.NewReader(c).ReadLine()
    48  	if err != nil {
    49  		return "", errors.Wrap(err, "failed to read line")
    50  	}
    51  	return string(line), nil
    52  }
    53  
    54  // GetResolver prepares the resolver from the environment and options
    55  func GetResolver(ctx gocontext.Context, clicontext *cli.Context) (remotes.Resolver, error) {
    56  	username := clicontext.String("user")
    57  	var secret string
    58  	if i := strings.IndexByte(username, ':'); i > 0 {
    59  		secret = username[i+1:]
    60  		username = username[0:i]
    61  	}
    62  	options := docker.ResolverOptions{
    63  		Tracker: PushTracker,
    64  	}
    65  	if username != "" {
    66  		if secret == "" {
    67  			fmt.Printf("Password: ")
    68  
    69  			var err error
    70  			secret, err = passwordPrompt()
    71  			if err != nil {
    72  				return nil, err
    73  			}
    74  
    75  			fmt.Print("\n")
    76  		}
    77  	} else if rt := clicontext.String("refresh"); rt != "" {
    78  		secret = rt
    79  	}
    80  
    81  	hostOptions := config.HostOptions{}
    82  	hostOptions.Credentials = func(host string) (string, string, error) {
    83  		// If host doesn't match...
    84  		// Only one host
    85  		return username, secret, nil
    86  	}
    87  	if clicontext.Bool("plain-http") {
    88  		hostOptions.DefaultScheme = "http"
    89  	}
    90  	defaultTLS, err := resolverDefaultTLS(clicontext)
    91  	if err != nil {
    92  		return nil, err
    93  	}
    94  	hostOptions.DefaultTLS = defaultTLS
    95  	if hostDir := clicontext.String("hosts-dir"); hostDir != "" {
    96  		hostOptions.HostDir = config.HostDirFromRoot(hostDir)
    97  	}
    98  
    99  	options.Hosts = config.ConfigureHosts(ctx, hostOptions)
   100  
   101  	return docker.NewResolver(options), nil
   102  }
   103  
   104  func resolverDefaultTLS(clicontext *cli.Context) (*tls.Config, error) {
   105  	config := &tls.Config{}
   106  
   107  	if clicontext.Bool("skip-verify") {
   108  		config.InsecureSkipVerify = true
   109  	}
   110  
   111  	if tlsRootPath := clicontext.String("tlscacert"); tlsRootPath != "" {
   112  		tlsRootData, err := ioutil.ReadFile(tlsRootPath)
   113  		if err != nil {
   114  			return nil, errors.Wrapf(err, "failed to read %q", tlsRootPath)
   115  		}
   116  
   117  		config.RootCAs = x509.NewCertPool()
   118  		if !config.RootCAs.AppendCertsFromPEM(tlsRootData) {
   119  			return nil, fmt.Errorf("failed to load TLS CAs from %q: invalid data", tlsRootPath)
   120  		}
   121  	}
   122  
   123  	tlsCertPath := clicontext.String("tlscert")
   124  	tlsKeyPath := clicontext.String("tlskey")
   125  	if tlsCertPath != "" || tlsKeyPath != "" {
   126  		if tlsCertPath == "" || tlsKeyPath == "" {
   127  			return nil, errors.New("flags --tlscert and --tlskey must be set together")
   128  		}
   129  		keyPair, err := tls.LoadX509KeyPair(tlsCertPath, tlsKeyPath)
   130  		if err != nil {
   131  			return nil, errors.Wrapf(err, "failed to load TLS client credentials (cert=%q, key=%q)", tlsCertPath, tlsKeyPath)
   132  		}
   133  		config.Certificates = []tls.Certificate{keyPair}
   134  	}
   135  
   136  	return config, nil
   137  }