github.com/outbrain/consul@v1.4.5/api/api_test.go (about) 1 package api 2 3 import ( 4 crand "crypto/rand" 5 "crypto/tls" 6 "fmt" 7 "net" 8 "net/http" 9 "os" 10 "path/filepath" 11 "reflect" 12 "runtime" 13 "strings" 14 "testing" 15 "time" 16 17 "github.com/hashicorp/consul/testutil" 18 "github.com/stretchr/testify/assert" 19 "github.com/stretchr/testify/require" 20 ) 21 22 type configCallback func(c *Config) 23 24 func makeClient(t *testing.T) (*Client, *testutil.TestServer) { 25 return makeClientWithConfig(t, nil, nil) 26 } 27 28 func makeClientWithoutConnect(t *testing.T) (*Client, *testutil.TestServer) { 29 return makeClientWithConfig(t, nil, func(serverConfig *testutil.TestServerConfig) { 30 serverConfig.Connect = nil 31 }) 32 } 33 34 func makeACLClient(t *testing.T) (*Client, *testutil.TestServer) { 35 return makeClientWithConfig(t, func(clientConfig *Config) { 36 clientConfig.Token = "root" 37 }, func(serverConfig *testutil.TestServerConfig) { 38 serverConfig.PrimaryDatacenter = "dc1" 39 serverConfig.ACLMasterToken = "root" 40 serverConfig.ACL.Enabled = true 41 serverConfig.ACLDefaultPolicy = "deny" 42 }) 43 } 44 45 func makeClientWithConfig( 46 t *testing.T, 47 cb1 configCallback, 48 cb2 testutil.ServerConfigCallback) (*Client, *testutil.TestServer) { 49 50 // Make client config 51 conf := DefaultConfig() 52 if cb1 != nil { 53 cb1(conf) 54 } 55 // Create server 56 server, err := testutil.NewTestServerConfigT(t, cb2) 57 if err != nil { 58 t.Fatal(err) 59 } 60 conf.Address = server.HTTPAddr 61 62 // Create client 63 client, err := NewClient(conf) 64 if err != nil { 65 server.Stop() 66 t.Fatalf("err: %v", err) 67 } 68 69 return client, server 70 } 71 72 func testKey() string { 73 buf := make([]byte, 16) 74 if _, err := crand.Read(buf); err != nil { 75 panic(fmt.Errorf("Failed to read random bytes: %v", err)) 76 } 77 78 return fmt.Sprintf("%08x-%04x-%04x-%04x-%12x", 79 buf[0:4], 80 buf[4:6], 81 buf[6:8], 82 buf[8:10], 83 buf[10:16]) 84 } 85 86 func TestAPI_DefaultConfig_env(t *testing.T) { 87 // t.Parallel() // DO NOT ENABLE !!! 88 // do not enable t.Parallel for this test since it modifies global state 89 // (environment) which has non-deterministic effects on the other tests 90 // which derive their default configuration from the environment 91 92 addr := "1.2.3.4:5678" 93 token := "abcd1234" 94 auth := "username:password" 95 96 os.Setenv(HTTPAddrEnvName, addr) 97 defer os.Setenv(HTTPAddrEnvName, "") 98 os.Setenv(HTTPTokenEnvName, token) 99 defer os.Setenv(HTTPTokenEnvName, "") 100 os.Setenv(HTTPAuthEnvName, auth) 101 defer os.Setenv(HTTPAuthEnvName, "") 102 os.Setenv(HTTPSSLEnvName, "1") 103 defer os.Setenv(HTTPSSLEnvName, "") 104 os.Setenv(HTTPCAFile, "ca.pem") 105 defer os.Setenv(HTTPCAFile, "") 106 os.Setenv(HTTPCAPath, "certs/") 107 defer os.Setenv(HTTPCAPath, "") 108 os.Setenv(HTTPClientCert, "client.crt") 109 defer os.Setenv(HTTPClientCert, "") 110 os.Setenv(HTTPClientKey, "client.key") 111 defer os.Setenv(HTTPClientKey, "") 112 os.Setenv(HTTPTLSServerName, "consul.test") 113 defer os.Setenv(HTTPTLSServerName, "") 114 os.Setenv(HTTPSSLVerifyEnvName, "0") 115 defer os.Setenv(HTTPSSLVerifyEnvName, "") 116 117 for i, config := range []*Config{DefaultConfig(), DefaultNonPooledConfig()} { 118 if config.Address != addr { 119 t.Errorf("expected %q to be %q", config.Address, addr) 120 } 121 if config.Token != token { 122 t.Errorf("expected %q to be %q", config.Token, token) 123 } 124 if config.HttpAuth == nil { 125 t.Fatalf("expected HttpAuth to be enabled") 126 } 127 if config.HttpAuth.Username != "username" { 128 t.Errorf("expected %q to be %q", config.HttpAuth.Username, "username") 129 } 130 if config.HttpAuth.Password != "password" { 131 t.Errorf("expected %q to be %q", config.HttpAuth.Password, "password") 132 } 133 if config.Scheme != "https" { 134 t.Errorf("expected %q to be %q", config.Scheme, "https") 135 } 136 if config.TLSConfig.CAFile != "ca.pem" { 137 t.Errorf("expected %q to be %q", config.TLSConfig.CAFile, "ca.pem") 138 } 139 if config.TLSConfig.CAPath != "certs/" { 140 t.Errorf("expected %q to be %q", config.TLSConfig.CAPath, "certs/") 141 } 142 if config.TLSConfig.CertFile != "client.crt" { 143 t.Errorf("expected %q to be %q", config.TLSConfig.CertFile, "client.crt") 144 } 145 if config.TLSConfig.KeyFile != "client.key" { 146 t.Errorf("expected %q to be %q", config.TLSConfig.KeyFile, "client.key") 147 } 148 if config.TLSConfig.Address != "consul.test" { 149 t.Errorf("expected %q to be %q", config.TLSConfig.Address, "consul.test") 150 } 151 if !config.TLSConfig.InsecureSkipVerify { 152 t.Errorf("expected SSL verification to be off") 153 } 154 155 // Use keep alives as a check for whether pooling is on or off. 156 if pooled := i == 0; pooled { 157 if config.Transport.DisableKeepAlives != false { 158 t.Errorf("expected keep alives to be enabled") 159 } 160 } else { 161 if config.Transport.DisableKeepAlives != true { 162 t.Errorf("expected keep alives to be disabled") 163 } 164 } 165 } 166 } 167 168 func TestAPI_SetupTLSConfig(t *testing.T) { 169 t.Parallel() 170 // A default config should result in a clean default client config. 171 tlsConfig := &TLSConfig{} 172 cc, err := SetupTLSConfig(tlsConfig) 173 if err != nil { 174 t.Fatalf("err: %v", err) 175 } 176 expected := &tls.Config{RootCAs: cc.RootCAs} 177 if !reflect.DeepEqual(cc, expected) { 178 t.Fatalf("bad: \n%v, \n%v", cc, expected) 179 } 180 181 // Try some address variations with and without ports. 182 tlsConfig.Address = "127.0.0.1" 183 cc, err = SetupTLSConfig(tlsConfig) 184 if err != nil { 185 t.Fatalf("err: %v", err) 186 } 187 expected.ServerName = "127.0.0.1" 188 if !reflect.DeepEqual(cc, expected) { 189 t.Fatalf("bad: %v", cc) 190 } 191 192 tlsConfig.Address = "127.0.0.1:80" 193 cc, err = SetupTLSConfig(tlsConfig) 194 if err != nil { 195 t.Fatalf("err: %v", err) 196 } 197 expected.ServerName = "127.0.0.1" 198 if !reflect.DeepEqual(cc, expected) { 199 t.Fatalf("bad: %v", cc) 200 } 201 202 tlsConfig.Address = "demo.consul.io:80" 203 cc, err = SetupTLSConfig(tlsConfig) 204 if err != nil { 205 t.Fatalf("err: %v", err) 206 } 207 expected.ServerName = "demo.consul.io" 208 if !reflect.DeepEqual(cc, expected) { 209 t.Fatalf("bad: %v", cc) 210 } 211 212 tlsConfig.Address = "[2001:db8:a0b:12f0::1]" 213 cc, err = SetupTLSConfig(tlsConfig) 214 if err != nil { 215 t.Fatalf("err: %v", err) 216 } 217 expected.ServerName = "[2001:db8:a0b:12f0::1]" 218 if !reflect.DeepEqual(cc, expected) { 219 t.Fatalf("bad: %v", cc) 220 } 221 222 tlsConfig.Address = "[2001:db8:a0b:12f0::1]:80" 223 cc, err = SetupTLSConfig(tlsConfig) 224 if err != nil { 225 t.Fatalf("err: %v", err) 226 } 227 expected.ServerName = "2001:db8:a0b:12f0::1" 228 if !reflect.DeepEqual(cc, expected) { 229 t.Fatalf("bad: %v", cc) 230 } 231 232 // Skip verification. 233 tlsConfig.InsecureSkipVerify = true 234 cc, err = SetupTLSConfig(tlsConfig) 235 if err != nil { 236 t.Fatalf("err: %v", err) 237 } 238 expected.InsecureSkipVerify = true 239 if !reflect.DeepEqual(cc, expected) { 240 t.Fatalf("bad: %v", cc) 241 } 242 243 // Make a new config that hits all the file parsers. 244 tlsConfig = &TLSConfig{ 245 CertFile: "../test/hostname/Alice.crt", 246 KeyFile: "../test/hostname/Alice.key", 247 CAFile: "../test/hostname/CertAuth.crt", 248 } 249 cc, err = SetupTLSConfig(tlsConfig) 250 if err != nil { 251 t.Fatalf("err: %v", err) 252 } 253 if len(cc.Certificates) != 1 { 254 t.Fatalf("missing certificate: %v", cc.Certificates) 255 } 256 if cc.RootCAs == nil { 257 t.Fatalf("didn't load root CAs") 258 } 259 260 // Use a directory to load the certs instead 261 cc, err = SetupTLSConfig(&TLSConfig{ 262 CAPath: "../test/ca_path", 263 }) 264 if err != nil { 265 t.Fatalf("err: %v", err) 266 } 267 if len(cc.RootCAs.Subjects()) != 2 { 268 t.Fatalf("didn't load root CAs") 269 } 270 } 271 272 func TestAPI_ClientTLSOptions(t *testing.T) { 273 t.Parallel() 274 // Start a server that verifies incoming HTTPS connections 275 _, srvVerify := makeClientWithConfig(t, nil, func(conf *testutil.TestServerConfig) { 276 conf.CAFile = "../test/client_certs/rootca.crt" 277 conf.CertFile = "../test/client_certs/server.crt" 278 conf.KeyFile = "../test/client_certs/server.key" 279 conf.VerifyIncomingHTTPS = true 280 }) 281 defer srvVerify.Stop() 282 283 // Start a server without VerifyIncomingHTTPS 284 _, srvNoVerify := makeClientWithConfig(t, nil, func(conf *testutil.TestServerConfig) { 285 conf.CAFile = "../test/client_certs/rootca.crt" 286 conf.CertFile = "../test/client_certs/server.crt" 287 conf.KeyFile = "../test/client_certs/server.key" 288 conf.VerifyIncomingHTTPS = false 289 }) 290 defer srvNoVerify.Stop() 291 292 // Client without a cert 293 t.Run("client without cert, validation", func(t *testing.T) { 294 client, err := NewClient(&Config{ 295 Address: srvVerify.HTTPSAddr, 296 Scheme: "https", 297 TLSConfig: TLSConfig{ 298 Address: "consul.test", 299 CAFile: "../test/client_certs/rootca.crt", 300 }, 301 }) 302 if err != nil { 303 t.Fatal(err) 304 } 305 306 // Should fail 307 _, err = client.Agent().Self() 308 if err == nil || !strings.Contains(err.Error(), "bad certificate") { 309 t.Fatal(err) 310 } 311 }) 312 313 // Client with a valid cert 314 t.Run("client with cert, validation", func(t *testing.T) { 315 client, err := NewClient(&Config{ 316 Address: srvVerify.HTTPSAddr, 317 Scheme: "https", 318 TLSConfig: TLSConfig{ 319 Address: "consul.test", 320 CAFile: "../test/client_certs/rootca.crt", 321 CertFile: "../test/client_certs/client.crt", 322 KeyFile: "../test/client_certs/client.key", 323 }, 324 }) 325 if err != nil { 326 t.Fatal(err) 327 } 328 329 // Should succeed 330 _, err = client.Agent().Self() 331 if err != nil { 332 t.Fatal(err) 333 } 334 }) 335 336 // Client without a cert 337 t.Run("client without cert, no validation", func(t *testing.T) { 338 client, err := NewClient(&Config{ 339 Address: srvNoVerify.HTTPSAddr, 340 Scheme: "https", 341 TLSConfig: TLSConfig{ 342 Address: "consul.test", 343 CAFile: "../test/client_certs/rootca.crt", 344 }, 345 }) 346 if err != nil { 347 t.Fatal(err) 348 } 349 350 // Should succeed 351 _, err = client.Agent().Self() 352 if err != nil { 353 t.Fatal(err) 354 } 355 }) 356 357 // Client with a valid cert 358 t.Run("client with cert, no validation", func(t *testing.T) { 359 client, err := NewClient(&Config{ 360 Address: srvNoVerify.HTTPSAddr, 361 Scheme: "https", 362 TLSConfig: TLSConfig{ 363 Address: "consul.test", 364 CAFile: "../test/client_certs/rootca.crt", 365 CertFile: "../test/client_certs/client.crt", 366 KeyFile: "../test/client_certs/client.key", 367 }, 368 }) 369 if err != nil { 370 t.Fatal(err) 371 } 372 373 // Should succeed 374 _, err = client.Agent().Self() 375 if err != nil { 376 t.Fatal(err) 377 } 378 }) 379 } 380 381 func TestAPI_SetQueryOptions(t *testing.T) { 382 t.Parallel() 383 c, s := makeClient(t) 384 defer s.Stop() 385 386 assert := assert.New(t) 387 388 r := c.newRequest("GET", "/v1/kv/foo") 389 q := &QueryOptions{ 390 Datacenter: "foo", 391 AllowStale: true, 392 RequireConsistent: true, 393 WaitIndex: 1000, 394 WaitTime: 100 * time.Second, 395 Token: "12345", 396 Near: "nodex", 397 } 398 r.setQueryOptions(q) 399 400 if r.params.Get("dc") != "foo" { 401 t.Fatalf("bad: %v", r.params) 402 } 403 if _, ok := r.params["stale"]; !ok { 404 t.Fatalf("bad: %v", r.params) 405 } 406 if _, ok := r.params["consistent"]; !ok { 407 t.Fatalf("bad: %v", r.params) 408 } 409 if r.params.Get("index") != "1000" { 410 t.Fatalf("bad: %v", r.params) 411 } 412 if r.params.Get("wait") != "100000ms" { 413 t.Fatalf("bad: %v", r.params) 414 } 415 if r.header.Get("X-Consul-Token") != "12345" { 416 t.Fatalf("bad: %v", r.header) 417 } 418 if r.params.Get("near") != "nodex" { 419 t.Fatalf("bad: %v", r.params) 420 } 421 assert.Equal("", r.header.Get("Cache-Control")) 422 423 r = c.newRequest("GET", "/v1/kv/foo") 424 q = &QueryOptions{ 425 UseCache: true, 426 MaxAge: 30 * time.Second, 427 StaleIfError: 345678 * time.Millisecond, // Fractional seconds should be rounded 428 } 429 r.setQueryOptions(q) 430 431 _, ok := r.params["cached"] 432 assert.True(ok) 433 assert.Equal("max-age=30, stale-if-error=346", r.header.Get("Cache-Control")) 434 } 435 436 func TestAPI_SetWriteOptions(t *testing.T) { 437 t.Parallel() 438 c, s := makeClient(t) 439 defer s.Stop() 440 441 r := c.newRequest("GET", "/v1/kv/foo") 442 q := &WriteOptions{ 443 Datacenter: "foo", 444 Token: "23456", 445 } 446 r.setWriteOptions(q) 447 448 if r.params.Get("dc") != "foo" { 449 t.Fatalf("bad: %v", r.params) 450 } 451 if r.header.Get("X-Consul-Token") != "23456" { 452 t.Fatalf("bad: %v", r.header) 453 } 454 } 455 456 func TestAPI_RequestToHTTP(t *testing.T) { 457 t.Parallel() 458 c, s := makeClient(t) 459 defer s.Stop() 460 461 r := c.newRequest("DELETE", "/v1/kv/foo") 462 q := &QueryOptions{ 463 Datacenter: "foo", 464 } 465 r.setQueryOptions(q) 466 req, err := r.toHTTP() 467 if err != nil { 468 t.Fatalf("err: %v", err) 469 } 470 471 if req.Method != "DELETE" { 472 t.Fatalf("bad: %v", req) 473 } 474 if req.URL.RequestURI() != "/v1/kv/foo?dc=foo" { 475 t.Fatalf("bad: %v", req) 476 } 477 } 478 479 func TestAPI_ParseQueryMeta(t *testing.T) { 480 t.Parallel() 481 resp := &http.Response{ 482 Header: make(map[string][]string), 483 } 484 resp.Header.Set("X-Consul-Index", "12345") 485 resp.Header.Set("X-Consul-LastContact", "80") 486 resp.Header.Set("X-Consul-KnownLeader", "true") 487 resp.Header.Set("X-Consul-Translate-Addresses", "true") 488 489 qm := &QueryMeta{} 490 if err := parseQueryMeta(resp, qm); err != nil { 491 t.Fatalf("err: %v", err) 492 } 493 494 if qm.LastIndex != 12345 { 495 t.Fatalf("Bad: %v", qm) 496 } 497 if qm.LastContact != 80*time.Millisecond { 498 t.Fatalf("Bad: %v", qm) 499 } 500 if !qm.KnownLeader { 501 t.Fatalf("Bad: %v", qm) 502 } 503 if !qm.AddressTranslationEnabled { 504 t.Fatalf("Bad: %v", qm) 505 } 506 } 507 508 func TestAPI_UnixSocket(t *testing.T) { 509 t.Parallel() 510 if runtime.GOOS == "windows" { 511 t.SkipNow() 512 } 513 514 tempDir := testutil.TempDir(t, "consul") 515 defer os.RemoveAll(tempDir) 516 socket := filepath.Join(tempDir, "test.sock") 517 518 c, s := makeClientWithConfig(t, func(c *Config) { 519 c.Address = "unix://" + socket 520 }, func(c *testutil.TestServerConfig) { 521 c.Addresses = &testutil.TestAddressConfig{ 522 HTTP: "unix://" + socket, 523 } 524 }) 525 defer s.Stop() 526 527 agent := c.Agent() 528 529 info, err := agent.Self() 530 if err != nil { 531 t.Fatalf("err: %s", err) 532 } 533 if info["Config"]["NodeName"].(string) == "" { 534 t.Fatalf("bad: %v", info) 535 } 536 } 537 538 func TestAPI_durToMsec(t *testing.T) { 539 t.Parallel() 540 if ms := durToMsec(0); ms != "0ms" { 541 t.Fatalf("bad: %s", ms) 542 } 543 544 if ms := durToMsec(time.Millisecond); ms != "1ms" { 545 t.Fatalf("bad: %s", ms) 546 } 547 548 if ms := durToMsec(time.Microsecond); ms != "1ms" { 549 t.Fatalf("bad: %s", ms) 550 } 551 552 if ms := durToMsec(5 * time.Millisecond); ms != "5ms" { 553 t.Fatalf("bad: %s", ms) 554 } 555 } 556 557 func TestAPI_IsRetryableError(t *testing.T) { 558 t.Parallel() 559 if IsRetryableError(nil) { 560 t.Fatal("should not be a retryable error") 561 } 562 563 if IsRetryableError(fmt.Errorf("not the error you are looking for")) { 564 t.Fatal("should not be a retryable error") 565 } 566 567 if !IsRetryableError(fmt.Errorf(serverError)) { 568 t.Fatal("should be a retryable error") 569 } 570 571 if !IsRetryableError(&net.OpError{Err: fmt.Errorf("network conn error")}) { 572 t.Fatal("should be a retryable error") 573 } 574 } 575 576 func TestAPI_GenerateEnv(t *testing.T) { 577 t.Parallel() 578 579 c := &Config{ 580 Address: "127.0.0.1:8500", 581 Token: "test", 582 Scheme: "http", 583 TLSConfig: TLSConfig{ 584 CAFile: "", 585 CAPath: "", 586 CertFile: "", 587 KeyFile: "", 588 Address: "", 589 InsecureSkipVerify: true, 590 }, 591 } 592 593 expected := []string{ 594 "CONSUL_HTTP_ADDR=127.0.0.1:8500", 595 "CONSUL_HTTP_TOKEN=test", 596 "CONSUL_HTTP_SSL=false", 597 "CONSUL_CACERT=", 598 "CONSUL_CAPATH=", 599 "CONSUL_CLIENT_CERT=", 600 "CONSUL_CLIENT_KEY=", 601 "CONSUL_TLS_SERVER_NAME=", 602 "CONSUL_HTTP_SSL_VERIFY=false", 603 "CONSUL_HTTP_AUTH=", 604 } 605 606 require.Equal(t, expected, c.GenerateEnv()) 607 } 608 609 func TestAPI_GenerateEnvHTTPS(t *testing.T) { 610 t.Parallel() 611 612 c := &Config{ 613 Address: "127.0.0.1:8500", 614 Token: "test", 615 Scheme: "https", 616 TLSConfig: TLSConfig{ 617 CAFile: "/var/consul/ca.crt", 618 CAPath: "/var/consul/ca.dir", 619 CertFile: "/var/consul/server.crt", 620 KeyFile: "/var/consul/ssl/server.key", 621 Address: "127.0.0.1:8500", 622 InsecureSkipVerify: false, 623 }, 624 HttpAuth: &HttpBasicAuth{ 625 Username: "user", 626 Password: "password", 627 }, 628 } 629 630 expected := []string{ 631 "CONSUL_HTTP_ADDR=127.0.0.1:8500", 632 "CONSUL_HTTP_TOKEN=test", 633 "CONSUL_HTTP_SSL=true", 634 "CONSUL_CACERT=/var/consul/ca.crt", 635 "CONSUL_CAPATH=/var/consul/ca.dir", 636 "CONSUL_CLIENT_CERT=/var/consul/server.crt", 637 "CONSUL_CLIENT_KEY=/var/consul/ssl/server.key", 638 "CONSUL_TLS_SERVER_NAME=127.0.0.1:8500", 639 "CONSUL_HTTP_SSL_VERIFY=true", 640 "CONSUL_HTTP_AUTH=user:password", 641 } 642 643 require.Equal(t, expected, c.GenerateEnv()) 644 }