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