From 58a1de9b59594948df152f0003e759b77bcaa56a Mon Sep 17 00:00:00 2001 From: Derek McGowan Date: Sun, 19 Jul 2015 22:56:10 -0700 Subject: [PATCH] Add integration cli trust tests Added notary server to docker base image. Created trust suite which runs trust server for running trusted commands. Signed-off-by: Derek McGowan (github: dmcgowan) --- Dockerfile | 10 ++ integration-cli/check_test.go | 23 ++++ integration-cli/docker_cli_pull_test.go | 31 +++++ integration-cli/docker_cli_push_test.go | 16 +++ integration-cli/docker_utils.go | 21 ++++ .../fixtures/notary/localhost.cert | 19 +++ integration-cli/fixtures/notary/localhost.key | 27 +++++ integration-cli/requirements.go | 10 ++ integration-cli/trust_server.go | 110 ++++++++++++++++++ 9 files changed, 267 insertions(+) create mode 100644 integration-cli/fixtures/notary/localhost.cert create mode 100644 integration-cli/fixtures/notary/localhost.key create mode 100644 integration-cli/trust_server.go diff --git a/Dockerfile b/Dockerfile index 941ec6f5f6..51b6cf08fe 100644 --- a/Dockerfile +++ b/Dockerfile @@ -136,6 +136,16 @@ RUN set -x \ go build -o /usr/local/bin/registry-v2 github.com/docker/distribution/cmd/registry \ && rm -rf "$GOPATH" +# Install notary server +ENV NOTARY_COMMIT 77bced079e83d80f40c1f0a544b1a8a3b97fb052 +RUN set -x \ + && export GOPATH="$(mktemp -d)" \ + && git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \ + && (cd "$GOPATH/src/github.com/docker/notary" && git checkout -q "$NOTARY_COMMIT") \ + && GOPATH="$GOPATH/src/github.com/docker/notary/Godeps/_workspace:$GOPATH" \ + go build -o /usr/local/bin/notary-server github.com/docker/notary/cmd/notary-server \ + && rm -rf "$GOPATH" + # Get the "docker-py" source so we can run their integration tests ENV DOCKER_PY_COMMIT 8a87001d09852058f08a807ab6e8491d57ca1e88 RUN git clone https://github.com/docker/docker-py.git /docker-py \ diff --git a/integration-cli/check_test.go b/integration-cli/check_test.go index 329cfc120c..5061008acb 100644 --- a/integration-cli/check_test.go +++ b/integration-cli/check_test.go @@ -61,3 +61,26 @@ func (s *DockerDaemonSuite) TearDownTest(c *check.C) { s.d.Stop() s.ds.TearDownTest(c) } + +func init() { + check.Suite(&DockerTrustSuite{ + ds: &DockerSuite{}, + }) +} + +type DockerTrustSuite struct { + ds *DockerSuite + reg *testRegistryV2 + not *testNotary +} + +func (s *DockerTrustSuite) SetUpTest(c *check.C) { + s.reg = setupRegistry(c) + s.not = setupNotary(c) +} + +func (s *DockerTrustSuite) TearDownTest(c *check.C) { + s.reg.Close() + s.not.Close() + s.ds.TearDownTest(c) +} diff --git a/integration-cli/docker_cli_pull_test.go b/integration-cli/docker_cli_pull_test.go index cc1cdaece0..1c7810a35b 100644 --- a/integration-cli/docker_cli_pull_test.go +++ b/integration-cli/docker_cli_pull_test.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "os/exec" "strings" "github.com/go-check/check" @@ -151,3 +152,33 @@ func (s *DockerSuite) TestPullImageWithAllTagFromCentralRegistry(c *check.C) { c.Fatalf("Pulling with all tags should get more images") } } + +func (s *DockerTrustSuite) TestTrustedPull(c *check.C) { + repoName := fmt.Sprintf("%v/dockercli/trusted:latest", privateRegistryURL) + // tag the image and upload it to the private registry + dockerCmd(c, "tag", "busybox", repoName) + + pushCmd := exec.Command(dockerBinary, "push", repoName) + s.trustedCmd(pushCmd) + out, _, err := runCommandWithOutput(pushCmd) + if err != nil { + c.Fatalf("Error running trusted push: %s\n%s", err, out) + } + if !strings.Contains(string(out), "Signing and pushing trust metadata") { + c.Fatalf("Missing expected output on trusted push:\n%s", out) + } + + dockerCmd(c, "rmi", repoName) + + // Try pull + pullCmd := exec.Command(dockerBinary, "pull", repoName) + s.trustedCmd(pullCmd) + out, _, err = runCommandWithOutput(pullCmd) + if err != nil { + c.Fatalf("Error running trusted pull: %s\n%s", err, out) + } + + if !strings.Contains(string(out), "Tagging") { + c.Fatalf("Missing expected output on trusted push:\n%s", out) + } +} diff --git a/integration-cli/docker_cli_push_test.go b/integration-cli/docker_cli_push_test.go index 0cc9b0a6fa..1bbb925913 100644 --- a/integration-cli/docker_cli_push_test.go +++ b/integration-cli/docker_cli_push_test.go @@ -143,3 +143,19 @@ func (s *DockerRegistrySuite) TestPushEmptyLayer(c *check.C) { c.Fatalf("pushing the image to the private registry has failed: %s, %v", out, err) } } + +func (s *DockerTrustSuite) TestTrustedPush(c *check.C) { + repoName := fmt.Sprintf("%v/dockercli/trusted:latest", privateRegistryURL) + // tag the image and upload it to the private registry + dockerCmd(c, "tag", "busybox", repoName) + + pushCmd := exec.Command(dockerBinary, "push", repoName) + s.trustedCmd(pushCmd) + out, _, err := runCommandWithOutput(pushCmd) + if err != nil { + c.Fatalf("Error running trusted push: %s\n%s", err, out) + } + if !strings.Contains(string(out), "Signing and pushing trust metadata") { + c.Fatalf("Missing expected output on trusted push:\n%s", out) + } +} diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 87c6504343..a7576ebc65 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -1260,6 +1260,27 @@ func setupRegistry(c *check.C) *testRegistryV2 { return reg } +func setupNotary(c *check.C) *testNotary { + testRequires(c, NotaryHosting) + ts, err := newTestNotary(c) + if err != nil { + c.Fatal(err) + } + + // Wait for notary to be ready to serve requests. + for i := 1; i <= 5; i++ { + if err = ts.Ping(); err == nil { + break + } + time.Sleep(10 * time.Millisecond * time.Duration(i*i)) + } + + if err != nil { + c.Fatalf("Timeout waiting for test notary to become available: %s", err) + } + return ts +} + // appendBaseEnv appends the minimum set of environment variables to exec the // docker cli binary for testing with correct configuration to the given env // list. diff --git a/integration-cli/fixtures/notary/localhost.cert b/integration-cli/fixtures/notary/localhost.cert new file mode 100644 index 0000000000..d1233a1b06 --- /dev/null +++ b/integration-cli/fixtures/notary/localhost.cert @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDCTCCAfOgAwIBAgIQTOoFF+ypXwgdXnXHuCTvYDALBgkqhkiG9w0BAQswJjER +MA8GA1UEChMIUXVpY2tUTFMxETAPBgNVBAMTCFF1aWNrVExTMB4XDTE1MDcxNzE5 +NDg1M1oXDTE4MDcwMTE5NDg1M1owJzERMA8GA1UEChMIUXVpY2tUTFMxEjAQBgNV +BAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMDO +qvTBAi0ApXLfe90ApJkdkRGwF838Qzt1UFSxomu5fHRV6l3FjX5XCVHiFQ4w3ROh +dMOu9NahfGLJv9VvWU2MV3YoY9Y7lIXpKwnK1v064wuls4nPh13BUWKQKofcY/e2 +qaSPd6/qmSRc/kJUvOI9jZMSX6ZRPu9K4PCqm2CivlbLq9UYuo1AbRGfuqHRvTxg +mQG7WQCzGSvSjuSg5qX3TEh0HckTczJG9ODULNRWNE7ld0W4sfv4VF8R7Uc/G7LO +8QwLCZ9TIl3gYMPCrhUL3Q6z9Jnn1SQS4mhDnPi6ugRYO1X8k3jjdxV9C2sXwUvN +OZI1rLEWl9TJNA7ZXtMCAwEAAaM2MDQwDgYDVR0PAQH/BAQDAgCgMAwGA1UdEwEB +/wQCMAAwFAYDVR0RBA0wC4IJbG9jYWxob3N0MAsGCSqGSIb3DQEBCwOCAQEAH6iq +kM2+UMukGDLEQKHHiauioWJlHDlLXv76bJiNfjSz94B/2XOQMb9PT04//tnGUyPK +K8Dx7RoxSodU6T5VRiz/A36mLOvt2t3bcL/1nHf9sAOHcexGtnCbQbW91V7RKfIL +sjiLNFDkQ9VfVNY+ynQptZoyH1sy07+dplfkIiPzRs5WuVAnEGsX3r6BrhgUITzi +g1B4kpmGZIohP4m6ZEBY5xuo/NQ0+GhjAENQMU38GpuoMyFS0i0dGcbx8weqnI/B +Er/qa0+GE/rBnWY8TiRow8dzpneSFQnUZpJ4EwD9IoOIDHo7k2Nbz2P50HMiCXZf +4RqzctVssRlrRVnO5w== +-----END CERTIFICATE----- diff --git a/integration-cli/fixtures/notary/localhost.key b/integration-cli/fixtures/notary/localhost.key new file mode 100644 index 0000000000..d7778359a3 --- /dev/null +++ b/integration-cli/fixtures/notary/localhost.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAwM6q9MECLQClct973QCkmR2REbAXzfxDO3VQVLGia7l8dFXq +XcWNflcJUeIVDjDdE6F0w6701qF8Ysm/1W9ZTYxXdihj1juUhekrCcrW/TrjC6Wz +ic+HXcFRYpAqh9xj97appI93r+qZJFz+QlS84j2NkxJfplE+70rg8KqbYKK+Vsur +1Ri6jUBtEZ+6odG9PGCZAbtZALMZK9KO5KDmpfdMSHQdyRNzMkb04NQs1FY0TuV3 +Rbix+/hUXxHtRz8bss7xDAsJn1MiXeBgw8KuFQvdDrP0mefVJBLiaEOc+Lq6BFg7 +VfyTeON3FX0LaxfBS805kjWssRaX1Mk0Dtle0wIDAQABAoIBAHbuhNHZROhRn70O +Ui9vOBki/dt1ThnH5AkHQngb4t6kWjrAzILvW2p1cdBKr0ZDqftz+rzCbVD/5+Rg +Iq8bsnB9g23lWEBMHD/GJsAxmRA3hNooamk11IBmwTcVSsbnkdq5mEdkICYphjHC +Ey0DbEf6RBxWlx3WvAWLoNmTw6iFaOCH8IyLavPpe7kLbZc219oNUw2qjCnCXCZE +/NuViADHJBPN8r7g1gmyclJmTumdUK6oHgXEMMPe43vhReGcgcReK9QZjnTcIXPM +4oJOraw+BtoZXVvvIPnC+5ntoLFOzjIzM0kaveReZbdgffqF4zy2vRfCHhWssanc +7a0xR4ECgYEA3Xuvcqy5Xw+v/jVCO0VZj++Z7apA78dY4tWsPx5/0DUTTziTlXkC +ADduEbwX6HgZ/iLvA9j4C3Z4mO8qByby/6UoBU8NEe+PQt6fT7S+dKSP4uy5ZxVM +i5opkEyrJsMbve9Jrlj4bk5CICsydrZ+SBFHnpNGjbduGQick5LORWECgYEA3trt +gepteDGiUYmnnBgjbYtcD11RvpKC8Z/QwGnzN5vk4eBu8r7DkMcLN+SiHjAovlJo +r5j3EbF8sla1zBf/yySdQZFqUGcwtw7MaAKCLdhQl5WsViNMIx6p2OJapu0dzbv2 +KTXrnoRCafcH92k0dUX1ahE9eyc8KX6VhbWwXLMCgYATGCCuEDoC+gVAMzM8jOQF +xrBMjwr+IP+GvskUv/pg5tJ9V/FRR5dmkWDJ4p9lCUWkZTqZ6FCqHFKVTLkg2LjG +VWS34HLOAwskxrCRXJG22KEW/TWWr31j46yFpjZzJwrzOvftMfpo+BI3V8IH/f+x +EtxLzYKdoRy6x8VH67YgwQKBgHor2vjV45142FuK83AHa6SqOZXSuvWWrGJ6Ep7p +doSN2jRaLXi2S9AaznOdy6JxFGUCGJHrcccpXgsGrjNtFLXxJKTFa1sYtwQkALsk +ZOltJQF09D1krGC0driHntrUMvqOiKye+sS0DRS6cIuaCUAhUiELwoC5SaoV0zKy +IDUxAoGAOK8Xq+3/sqe79vTpw25RXl+nkAmOAeKjqf3Kh6jbnBhr81rmefyKXB9a +uj0b980tzUnliwA5cCOsyxfN2vASvMnJxFE721QZI04arlcPFHcFqCtmNnUYTcLp +0hgn/yLZptcoxpy+eTBu3eNsxz1Bu/Tx/198+2Wr3MbtGpLNIcA= +-----END RSA PRIVATE KEY----- diff --git a/integration-cli/requirements.go b/integration-cli/requirements.go index 9dc0892683..ce080d50d5 100644 --- a/integration-cli/requirements.go +++ b/integration-cli/requirements.go @@ -74,6 +74,16 @@ var ( }, fmt.Sprintf("Test requires an environment that can host %s in the same host", v2binary), } + NotaryHosting = testRequirement{ + func() bool { + // for now notary binary is built only if we're running inside + // container through `make test`. Figure that out by testing if + // notary-server binary is in PATH. + _, err := exec.LookPath(notaryBinary) + return err == nil + }, + fmt.Sprintf("Test requires an environment that can host %s in the same host", notaryBinary), + } NativeExecDriver = testRequirement{ func() bool { if daemonExecDriver == "" { diff --git a/integration-cli/trust_server.go b/integration-cli/trust_server.go new file mode 100644 index 0000000000..0205de620c --- /dev/null +++ b/integration-cli/trust_server.go @@ -0,0 +1,110 @@ +package main + +import ( + "fmt" + "io/ioutil" + "net" + "net/http" + "os" + "os/exec" + "path/filepath" + "time" + + "github.com/docker/docker/pkg/tlsconfig" + "github.com/go-check/check" +) + +var notaryBinary = "notary-server" + +type testNotary struct { + cmd *exec.Cmd + dir string +} + +func newTestNotary(c *check.C) (*testNotary, error) { + template := `{ + "server": { + "addr": "%s", + "tls_key_file": "fixtures/notary/localhost.key", + "tls_cert_file": "fixtures/notary/localhost.cert" + }, + "trust_service": { + "type": "local", + "hostname": "", + "port": "" + }, + "logging": { + "level": 5 + } +}` + tmp, err := ioutil.TempDir("", "notary-test-") + if err != nil { + return nil, err + } + confPath := filepath.Join(tmp, "config.json") + config, err := os.Create(confPath) + if err != nil { + return nil, err + } + if _, err := fmt.Fprintf(config, template, "localhost:4443"); err != nil { + os.RemoveAll(tmp) + return nil, err + } + + cmd := exec.Command(notaryBinary, "-config", confPath) + if err := cmd.Start(); err != nil { + os.RemoveAll(tmp) + if os.IsNotExist(err) { + c.Skip(err.Error()) + } + return nil, err + } + return &testNotary{ + cmd: cmd, + dir: tmp, + }, nil +} + +func (t *testNotary) address() string { + return "localhost:4443" +} + +func (t *testNotary) Ping() error { + tlsConfig := tlsconfig.ClientDefault + tlsConfig.InsecureSkipVerify = true + client := http.Client{ + Transport: &http.Transport{ + Proxy: http.ProxyFromEnvironment, + Dial: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + }).Dial, + TLSHandshakeTimeout: 10 * time.Second, + TLSClientConfig: &tlsConfig, + }, + } + resp, err := client.Get(fmt.Sprintf("https://%s/v2/", t.address())) + if err != nil { + return err + } + if resp.StatusCode != 200 { + return fmt.Errorf("notary ping replied with an unexpected status code %d", resp.StatusCode) + } + return nil +} + +func (t *testNotary) Close() { + t.cmd.Process.Kill() + os.RemoveAll(t.dir) +} + +func (s *DockerTrustSuite) trustedCmd(cmd *exec.Cmd) { + env := []string{ + "DOCKER_TRUST=1", + fmt.Sprintf("DOCKER_TRUST_SERVER=%s", s.not.address()), + "DOCKER_TRUST_ROOT_PASSPHRASE=12345678", + "DOCKER_TRUST_TARGET_PASSPHRASE=12345678", + "DOCKER_TRUST_SNAPSHOT_PASSPHRASE=12345678", + } + cmd.Env = append(os.Environ(), env...) +}