github.com/ssdev-go/moby@v17.12.1-ce-rc2+incompatible/integration-cli/trust_server_test.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"net"
     8  	"net/http"
     9  	"os"
    10  	"os/exec"
    11  	"path/filepath"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/docker/docker/api/types"
    16  	cliconfig "github.com/docker/docker/cli/config"
    17  	"github.com/docker/docker/integration-cli/checker"
    18  	"github.com/docker/docker/integration-cli/cli"
    19  	"github.com/docker/docker/integration-cli/fixtures/plugin"
    20  	"github.com/docker/docker/integration-cli/request"
    21  	"github.com/docker/go-connections/tlsconfig"
    22  	"github.com/go-check/check"
    23  	"github.com/gotestyourself/gotestyourself/icmd"
    24  )
    25  
    26  var notaryBinary = "notary"
    27  var notaryServerBinary = "notary-server"
    28  
    29  type keyPair struct {
    30  	Public  string
    31  	Private string
    32  }
    33  
    34  type testNotary struct {
    35  	cmd  *exec.Cmd
    36  	dir  string
    37  	keys []keyPair
    38  }
    39  
    40  const notaryHost = "localhost:4443"
    41  const notaryURL = "https://" + notaryHost
    42  
    43  var SuccessTagging = icmd.Expected{
    44  	Err: "Tagging",
    45  }
    46  
    47  var SuccessSigningAndPushing = icmd.Expected{
    48  	Out: "Signing and pushing trust metadata",
    49  }
    50  
    51  var SuccessDownloaded = icmd.Expected{
    52  	Out: "Status: Downloaded",
    53  }
    54  
    55  var SuccessDownloadedOnStderr = icmd.Expected{
    56  	Err: "Status: Downloaded",
    57  }
    58  
    59  func newTestNotary(c *check.C) (*testNotary, error) {
    60  	// generate server config
    61  	template := `{
    62  	"server": {
    63  		"http_addr": "%s",
    64  		"tls_key_file": "%s",
    65  		"tls_cert_file": "%s"
    66  	},
    67  	"trust_service": {
    68  		"type": "local",
    69  		"hostname": "",
    70  		"port": "",
    71  		"key_algorithm": "ed25519"
    72  	},
    73  	"logging": {
    74  		"level": "debug"
    75  	},
    76  	"storage": {
    77          "backend": "memory"
    78      }
    79  }`
    80  	tmp, err := ioutil.TempDir("", "notary-test-")
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  	confPath := filepath.Join(tmp, "config.json")
    85  	config, err := os.Create(confPath)
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  	defer config.Close()
    90  
    91  	workingDir, err := os.Getwd()
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  	if _, err := fmt.Fprintf(config, template, notaryHost, filepath.Join(workingDir, "fixtures/notary/localhost.key"), filepath.Join(workingDir, "fixtures/notary/localhost.cert")); err != nil {
    96  		os.RemoveAll(tmp)
    97  		return nil, err
    98  	}
    99  
   100  	// generate client config
   101  	clientConfPath := filepath.Join(tmp, "client-config.json")
   102  	clientConfig, err := os.Create(clientConfPath)
   103  	if err != nil {
   104  		return nil, err
   105  	}
   106  	defer clientConfig.Close()
   107  
   108  	template = `{
   109  	"trust_dir" : "%s",
   110  	"remote_server": {
   111  		"url": "%s",
   112  		"skipTLSVerify": true
   113  	}
   114  }`
   115  	if _, err = fmt.Fprintf(clientConfig, template, filepath.Join(cliconfig.Dir(), "trust"), notaryURL); err != nil {
   116  		os.RemoveAll(tmp)
   117  		return nil, err
   118  	}
   119  
   120  	// load key fixture filenames
   121  	var keys []keyPair
   122  	for i := 1; i < 5; i++ {
   123  		keys = append(keys, keyPair{
   124  			Public:  filepath.Join(workingDir, fmt.Sprintf("fixtures/notary/delgkey%v.crt", i)),
   125  			Private: filepath.Join(workingDir, fmt.Sprintf("fixtures/notary/delgkey%v.key", i)),
   126  		})
   127  	}
   128  
   129  	// run notary-server
   130  	cmd := exec.Command(notaryServerBinary, "-config", confPath)
   131  	if err := cmd.Start(); err != nil {
   132  		os.RemoveAll(tmp)
   133  		if os.IsNotExist(err) {
   134  			c.Skip(err.Error())
   135  		}
   136  		return nil, err
   137  	}
   138  
   139  	testNotary := &testNotary{
   140  		cmd:  cmd,
   141  		dir:  tmp,
   142  		keys: keys,
   143  	}
   144  
   145  	// Wait for notary to be ready to serve requests.
   146  	for i := 1; i <= 20; i++ {
   147  		if err = testNotary.Ping(); err == nil {
   148  			break
   149  		}
   150  		time.Sleep(10 * time.Millisecond * time.Duration(i*i))
   151  	}
   152  
   153  	if err != nil {
   154  		c.Fatalf("Timeout waiting for test notary to become available: %s", err)
   155  	}
   156  
   157  	return testNotary, nil
   158  }
   159  
   160  func (t *testNotary) Ping() error {
   161  	tlsConfig := tlsconfig.ClientDefault()
   162  	tlsConfig.InsecureSkipVerify = true
   163  	client := http.Client{
   164  		Transport: &http.Transport{
   165  			Proxy: http.ProxyFromEnvironment,
   166  			Dial: (&net.Dialer{
   167  				Timeout:   30 * time.Second,
   168  				KeepAlive: 30 * time.Second,
   169  			}).Dial,
   170  			TLSHandshakeTimeout: 10 * time.Second,
   171  			TLSClientConfig:     tlsConfig,
   172  		},
   173  	}
   174  	resp, err := client.Get(fmt.Sprintf("%s/v2/", notaryURL))
   175  	if err != nil {
   176  		return err
   177  	}
   178  	if resp.StatusCode != http.StatusOK {
   179  		return fmt.Errorf("notary ping replied with an unexpected status code %d", resp.StatusCode)
   180  	}
   181  	return nil
   182  }
   183  
   184  func (t *testNotary) Close() {
   185  	t.cmd.Process.Kill()
   186  	t.cmd.Process.Wait()
   187  	os.RemoveAll(t.dir)
   188  }
   189  
   190  func trustedCmd(cmd *icmd.Cmd) func() {
   191  	pwd := "12345678"
   192  	cmd.Env = append(cmd.Env, trustEnv(notaryURL, pwd, pwd)...)
   193  	return nil
   194  }
   195  
   196  func trustedCmdWithServer(server string) func(*icmd.Cmd) func() {
   197  	return func(cmd *icmd.Cmd) func() {
   198  		pwd := "12345678"
   199  		cmd.Env = append(cmd.Env, trustEnv(server, pwd, pwd)...)
   200  		return nil
   201  	}
   202  }
   203  
   204  func trustedCmdWithPassphrases(rootPwd, repositoryPwd string) func(*icmd.Cmd) func() {
   205  	return func(cmd *icmd.Cmd) func() {
   206  		cmd.Env = append(cmd.Env, trustEnv(notaryURL, rootPwd, repositoryPwd)...)
   207  		return nil
   208  	}
   209  }
   210  
   211  func trustEnv(server, rootPwd, repositoryPwd string) []string {
   212  	env := append(os.Environ(), []string{
   213  		"DOCKER_CONTENT_TRUST=1",
   214  		fmt.Sprintf("DOCKER_CONTENT_TRUST_SERVER=%s", server),
   215  		fmt.Sprintf("DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE=%s", rootPwd),
   216  		fmt.Sprintf("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE=%s", repositoryPwd),
   217  	}...)
   218  	return env
   219  }
   220  
   221  func (s *DockerTrustSuite) setupTrustedImage(c *check.C, name string) string {
   222  	repoName := fmt.Sprintf("%v/dockercli/%s:latest", privateRegistryURL, name)
   223  	// tag the image and upload it to the private registry
   224  	cli.DockerCmd(c, "tag", "busybox", repoName)
   225  	cli.Docker(cli.Args("push", repoName), trustedCmd).Assert(c, SuccessSigningAndPushing)
   226  	cli.DockerCmd(c, "rmi", repoName)
   227  	return repoName
   228  }
   229  
   230  func (s *DockerTrustSuite) setupTrustedplugin(c *check.C, source, name string) string {
   231  	repoName := fmt.Sprintf("%v/dockercli/%s:latest", privateRegistryURL, name)
   232  
   233  	client, err := request.NewClient()
   234  	c.Assert(err, checker.IsNil, check.Commentf("could not create test client"))
   235  
   236  	ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
   237  	err = plugin.Create(ctx, client, repoName)
   238  	cancel()
   239  	c.Assert(err, checker.IsNil, check.Commentf("could not create test plugin"))
   240  
   241  	// tag the image and upload it to the private registry
   242  	// TODO: shouldn't need to use the CLI to do trust
   243  	cli.Docker(cli.Args("plugin", "push", repoName), trustedCmd).Assert(c, SuccessSigningAndPushing)
   244  
   245  	ctx, cancel = context.WithTimeout(context.Background(), 60*time.Second)
   246  	err = client.PluginRemove(ctx, repoName, types.PluginRemoveOptions{Force: true})
   247  	cancel()
   248  	c.Assert(err, checker.IsNil, check.Commentf("failed to cleanup test plugin for trust suite"))
   249  	return repoName
   250  }
   251  
   252  func (s *DockerTrustSuite) notaryCmd(c *check.C, args ...string) string {
   253  	pwd := "12345678"
   254  	env := []string{
   255  		fmt.Sprintf("NOTARY_ROOT_PASSPHRASE=%s", pwd),
   256  		fmt.Sprintf("NOTARY_TARGETS_PASSPHRASE=%s", pwd),
   257  		fmt.Sprintf("NOTARY_SNAPSHOT_PASSPHRASE=%s", pwd),
   258  		fmt.Sprintf("NOTARY_DELEGATION_PASSPHRASE=%s", pwd),
   259  	}
   260  	result := icmd.RunCmd(icmd.Cmd{
   261  		Command: append([]string{notaryBinary, "-c", filepath.Join(s.not.dir, "client-config.json")}, args...),
   262  		Env:     append(os.Environ(), env...),
   263  	})
   264  	result.Assert(c, icmd.Success)
   265  	return result.Combined()
   266  }
   267  
   268  func (s *DockerTrustSuite) notaryInitRepo(c *check.C, repoName string) {
   269  	s.notaryCmd(c, "init", repoName)
   270  }
   271  
   272  func (s *DockerTrustSuite) notaryCreateDelegation(c *check.C, repoName, role string, pubKey string, paths ...string) {
   273  	pathsArg := "--all-paths"
   274  	if len(paths) > 0 {
   275  		pathsArg = "--paths=" + strings.Join(paths, ",")
   276  	}
   277  
   278  	s.notaryCmd(c, "delegation", "add", repoName, role, pubKey, pathsArg)
   279  }
   280  
   281  func (s *DockerTrustSuite) notaryPublish(c *check.C, repoName string) {
   282  	s.notaryCmd(c, "publish", repoName)
   283  }
   284  
   285  func (s *DockerTrustSuite) notaryImportKey(c *check.C, repoName, role string, privKey string) {
   286  	s.notaryCmd(c, "key", "import", privKey, "-g", repoName, "-r", role)
   287  }
   288  
   289  func (s *DockerTrustSuite) notaryListTargetsInRole(c *check.C, repoName, role string) map[string]string {
   290  	out := s.notaryCmd(c, "list", repoName, "-r", role)
   291  
   292  	// should look something like:
   293  	//    NAME                                 DIGEST                                SIZE (BYTES)    ROLE
   294  	// ------------------------------------------------------------------------------------------------------
   295  	//   latest   24a36bbc059b1345b7e8be0df20f1b23caa3602e85d42fff7ecd9d0bd255de56   1377           targets
   296  
   297  	targets := make(map[string]string)
   298  
   299  	// no target
   300  	lines := strings.Split(strings.TrimSpace(out), "\n")
   301  	if len(lines) == 1 && strings.Contains(out, "No targets present in this repository.") {
   302  		return targets
   303  	}
   304  
   305  	// otherwise, there is at least one target
   306  	c.Assert(len(lines), checker.GreaterOrEqualThan, 3)
   307  
   308  	for _, line := range lines[2:] {
   309  		tokens := strings.Fields(line)
   310  		c.Assert(tokens, checker.HasLen, 4)
   311  		targets[tokens[0]] = tokens[3]
   312  	}
   313  
   314  	return targets
   315  }
   316  
   317  func (s *DockerTrustSuite) assertTargetInRoles(c *check.C, repoName, target string, roles ...string) {
   318  	// check all the roles
   319  	for _, role := range roles {
   320  		targets := s.notaryListTargetsInRole(c, repoName, role)
   321  		roleName, ok := targets[target]
   322  		c.Assert(ok, checker.True)
   323  		c.Assert(roleName, checker.Equals, role)
   324  	}
   325  }
   326  
   327  func (s *DockerTrustSuite) assertTargetNotInRoles(c *check.C, repoName, target string, roles ...string) {
   328  	targets := s.notaryListTargetsInRole(c, repoName, "targets")
   329  
   330  	roleName, ok := targets[target]
   331  	if ok {
   332  		for _, role := range roles {
   333  			c.Assert(roleName, checker.Not(checker.Equals), role)
   334  		}
   335  	}
   336  }