github.com/vieux/docker@v0.6.3-0.20161004191708-e097c2a938c7/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 notaryClientEnv(cmd *exec.Cmd) { 215 pwd := "12345678" 216 env := []string{ 217 fmt.Sprintf("NOTARY_ROOT_PASSPHRASE=%s", pwd), 218 fmt.Sprintf("NOTARY_TARGETS_PASSPHRASE=%s", pwd), 219 fmt.Sprintf("NOTARY_SNAPSHOT_PASSPHRASE=%s", pwd), 220 fmt.Sprintf("NOTARY_DELEGATION_PASSPHRASE=%s", pwd), 221 } 222 cmd.Env = append(os.Environ(), env...) 223 } 224 225 func (s *DockerTrustSuite) notaryInitRepo(c *check.C, repoName string) { 226 initCmd := exec.Command(notaryBinary, "-c", filepath.Join(s.not.dir, "client-config.json"), "init", repoName) 227 notaryClientEnv(initCmd) 228 out, _, err := runCommandWithOutput(initCmd) 229 if err != nil { 230 c.Fatalf("Error initializing notary repository: %s\n", out) 231 } 232 } 233 234 func (s *DockerTrustSuite) notaryCreateDelegation(c *check.C, repoName, role string, pubKey string, paths ...string) { 235 pathsArg := "--all-paths" 236 if len(paths) > 0 { 237 pathsArg = "--paths=" + strings.Join(paths, ",") 238 } 239 240 delgCmd := exec.Command(notaryBinary, "-c", filepath.Join(s.not.dir, "client-config.json"), 241 "delegation", "add", repoName, role, pubKey, pathsArg) 242 notaryClientEnv(delgCmd) 243 out, _, err := runCommandWithOutput(delgCmd) 244 if err != nil { 245 c.Fatalf("Error adding %s role to notary repository: %s\n", role, out) 246 } 247 } 248 249 func (s *DockerTrustSuite) notaryPublish(c *check.C, repoName string) { 250 pubCmd := exec.Command(notaryBinary, "-c", filepath.Join(s.not.dir, "client-config.json"), "publish", repoName) 251 notaryClientEnv(pubCmd) 252 out, _, err := runCommandWithOutput(pubCmd) 253 if err != nil { 254 c.Fatalf("Error publishing notary repository: %s\n", out) 255 } 256 } 257 258 func (s *DockerTrustSuite) notaryImportKey(c *check.C, repoName, role string, privKey string) { 259 impCmd := exec.Command(notaryBinary, "-c", filepath.Join(s.not.dir, "client-config.json"), "key", 260 "import", privKey, "-g", repoName, "-r", role) 261 notaryClientEnv(impCmd) 262 out, _, err := runCommandWithOutput(impCmd) 263 if err != nil { 264 c.Fatalf("Error importing key to notary repository: %s\n", out) 265 } 266 } 267 268 func (s *DockerTrustSuite) notaryListTargetsInRole(c *check.C, repoName, role string) map[string]string { 269 listCmd := exec.Command(notaryBinary, "-c", filepath.Join(s.not.dir, "client-config.json"), "list", 270 repoName, "-r", role) 271 notaryClientEnv(listCmd) 272 out, _, err := runCommandWithOutput(listCmd) 273 if err != nil { 274 c.Fatalf("Error listing targets in notary repository: %s\n", out) 275 } 276 277 // should look something like: 278 // NAME DIGEST SIZE (BYTES) ROLE 279 // ------------------------------------------------------------------------------------------------------ 280 // latest 24a36bbc059b1345b7e8be0df20f1b23caa3602e85d42fff7ecd9d0bd255de56 1377 targets 281 282 targets := make(map[string]string) 283 284 // no target 285 lines := strings.Split(strings.TrimSpace(out), "\n") 286 if len(lines) == 1 && strings.Contains(out, "No targets present in this repository.") { 287 return targets 288 } 289 290 // otherwise, there is at least one target 291 c.Assert(len(lines), checker.GreaterOrEqualThan, 3) 292 293 for _, line := range lines[2:] { 294 tokens := strings.Fields(line) 295 c.Assert(tokens, checker.HasLen, 4) 296 targets[tokens[0]] = tokens[3] 297 } 298 299 return targets 300 } 301 302 func (s *DockerTrustSuite) assertTargetInRoles(c *check.C, repoName, target string, roles ...string) { 303 // check all the roles 304 for _, role := range roles { 305 targets := s.notaryListTargetsInRole(c, repoName, role) 306 roleName, ok := targets[target] 307 c.Assert(ok, checker.True) 308 c.Assert(roleName, checker.Equals, role) 309 } 310 } 311 312 func (s *DockerTrustSuite) assertTargetNotInRoles(c *check.C, repoName, target string, roles ...string) { 313 targets := s.notaryListTargetsInRole(c, repoName, "targets") 314 315 roleName, ok := targets[target] 316 if ok { 317 for _, role := range roles { 318 c.Assert(roleName, checker.Not(checker.Equals), role) 319 } 320 } 321 }