1package consul 2 3import ( 4 "context" 5 "fmt" 6 "math/rand" 7 "os" 8 "reflect" 9 "strings" 10 "sync" 11 "testing" 12 "time" 13 14 "github.com/hashicorp/consul/api" 15 log "github.com/hashicorp/go-hclog" 16 "github.com/hashicorp/vault/helper/testhelpers/consul" 17 "github.com/hashicorp/vault/sdk/helper/logging" 18 "github.com/hashicorp/vault/sdk/helper/strutil" 19 "github.com/hashicorp/vault/sdk/physical" 20) 21 22type consulConf map[string]string 23 24var ( 25 addrCount int = 0 26) 27 28func testConsulBackend(t *testing.T) *ConsulBackend { 29 return testConsulBackendConfig(t, &consulConf{}) 30} 31 32func testConsulBackendConfig(t *testing.T, conf *consulConf) *ConsulBackend { 33 logger := logging.NewVaultLogger(log.Debug) 34 35 be, err := NewConsulBackend(*conf, logger) 36 if err != nil { 37 t.Fatalf("Expected Consul to initialize: %v", err) 38 } 39 40 c, ok := be.(*ConsulBackend) 41 if !ok { 42 t.Fatalf("Expected ConsulBackend") 43 } 44 45 return c 46} 47 48func testConsul_testConsulBackend(t *testing.T) { 49 c := testConsulBackend(t) 50 if c == nil { 51 t.Fatalf("bad") 52 } 53} 54 55func testActiveFunc(activePct float64) physical.ActiveFunction { 56 return func() bool { 57 var active bool 58 standbyProb := rand.Float64() 59 if standbyProb > activePct { 60 active = true 61 } 62 return active 63 } 64} 65 66func testSealedFunc(sealedPct float64) physical.SealedFunction { 67 return func() bool { 68 var sealed bool 69 unsealedProb := rand.Float64() 70 if unsealedProb > sealedPct { 71 sealed = true 72 } 73 return sealed 74 } 75} 76 77func testPerformanceStandbyFunc(perfPct float64) physical.PerformanceStandbyFunction { 78 return func() bool { 79 var ps bool 80 unsealedProb := rand.Float64() 81 if unsealedProb > perfPct { 82 ps = true 83 } 84 return ps 85 } 86} 87 88func TestConsul_ServiceTags(t *testing.T) { 89 consulConfig := map[string]string{ 90 "path": "seaTech/", 91 "service": "astronomy", 92 "service_tags": "deadbeef, cafeefac, deadc0de, feedface", 93 "redirect_addr": "http://127.0.0.2:8200", 94 "check_timeout": "6s", 95 "address": "127.0.0.2", 96 "scheme": "https", 97 "token": "deadbeef-cafeefac-deadc0de-feedface", 98 "max_parallel": "4", 99 "disable_registration": "false", 100 } 101 logger := logging.NewVaultLogger(log.Debug) 102 103 be, err := NewConsulBackend(consulConfig, logger) 104 if err != nil { 105 t.Fatal(err) 106 } 107 108 c, ok := be.(*ConsulBackend) 109 if !ok { 110 t.Fatalf("failed to create physical Consul backend") 111 } 112 113 expected := []string{"deadbeef", "cafeefac", "deadc0de", "feedface"} 114 actual := c.fetchServiceTags(false, false) 115 if !strutil.EquivalentSlices(actual, append(expected, "standby")) { 116 t.Fatalf("bad: expected:%s actual:%s", append(expected, "standby"), actual) 117 } 118 119 actual = c.fetchServiceTags(true, false) 120 if !strutil.EquivalentSlices(actual, append(expected, "active")) { 121 t.Fatalf("bad: expected:%s actual:%s", append(expected, "active"), actual) 122 } 123 124 actual = c.fetchServiceTags(false, true) 125 if !strutil.EquivalentSlices(actual, append(expected, "performance-standby")) { 126 t.Fatalf("bad: expected:%s actual:%s", append(expected, "performance-standby"), actual) 127 } 128 129 actual = c.fetchServiceTags(true, true) 130 if !strutil.EquivalentSlices(actual, append(expected, "performance-standby")) { 131 t.Fatalf("bad: expected:%s actual:%s", append(expected, "performance-standby"), actual) 132 } 133} 134 135func TestConsul_ServiceAddress(t *testing.T) { 136 tests := []struct { 137 consulConfig map[string]string 138 serviceAddrNil bool 139 }{ 140 { 141 consulConfig: map[string]string{ 142 "service_address": "", 143 }, 144 }, 145 { 146 consulConfig: map[string]string{ 147 "service_address": "vault.example.com", 148 }, 149 }, 150 { 151 serviceAddrNil: true, 152 }, 153 } 154 155 for _, test := range tests { 156 logger := logging.NewVaultLogger(log.Debug) 157 158 be, err := NewConsulBackend(test.consulConfig, logger) 159 if err != nil { 160 t.Fatalf("expected Consul to initialize: %v", err) 161 } 162 163 c, ok := be.(*ConsulBackend) 164 if !ok { 165 t.Fatalf("Expected ConsulBackend") 166 } 167 168 if test.serviceAddrNil { 169 if c.serviceAddress != nil { 170 t.Fatalf("expected service address to be nil") 171 } 172 } else { 173 if c.serviceAddress == nil { 174 t.Fatalf("did not expect service address to be nil") 175 } 176 } 177 } 178} 179 180func TestConsul_newConsulBackend(t *testing.T) { 181 tests := []struct { 182 name string 183 consulConfig map[string]string 184 fail bool 185 redirectAddr string 186 checkTimeout time.Duration 187 path string 188 service string 189 address string 190 scheme string 191 token string 192 max_parallel int 193 disableReg bool 194 consistencyMode string 195 }{ 196 { 197 name: "Valid default config", 198 consulConfig: map[string]string{}, 199 checkTimeout: 5 * time.Second, 200 redirectAddr: "http://127.0.0.1:8200", 201 path: "vault/", 202 service: "vault", 203 address: "127.0.0.1:8500", 204 scheme: "http", 205 token: "", 206 max_parallel: 4, 207 disableReg: false, 208 consistencyMode: "default", 209 }, 210 { 211 name: "Valid modified config", 212 consulConfig: map[string]string{ 213 "path": "seaTech/", 214 "service": "astronomy", 215 "redirect_addr": "http://127.0.0.2:8200", 216 "check_timeout": "6s", 217 "address": "127.0.0.2", 218 "scheme": "https", 219 "token": "deadbeef-cafeefac-deadc0de-feedface", 220 "max_parallel": "4", 221 "disable_registration": "false", 222 "consistency_mode": "strong", 223 }, 224 checkTimeout: 6 * time.Second, 225 path: "seaTech/", 226 service: "astronomy", 227 redirectAddr: "http://127.0.0.2:8200", 228 address: "127.0.0.2", 229 scheme: "https", 230 token: "deadbeef-cafeefac-deadc0de-feedface", 231 max_parallel: 4, 232 consistencyMode: "strong", 233 }, 234 { 235 name: "Unix socket", 236 consulConfig: map[string]string{ 237 "address": "unix:///tmp/.consul.http.sock", 238 }, 239 address: "/tmp/.consul.http.sock", 240 scheme: "http", // Default, not overridden? 241 242 // Defaults 243 checkTimeout: 5 * time.Second, 244 redirectAddr: "http://127.0.0.1:8200", 245 path: "vault/", 246 service: "vault", 247 token: "", 248 max_parallel: 4, 249 disableReg: false, 250 consistencyMode: "default", 251 }, 252 { 253 name: "Scheme in address", 254 consulConfig: map[string]string{ 255 "address": "https://127.0.0.2:5000", 256 }, 257 address: "127.0.0.2:5000", 258 scheme: "https", 259 260 // Defaults 261 checkTimeout: 5 * time.Second, 262 redirectAddr: "http://127.0.0.1:8200", 263 path: "vault/", 264 service: "vault", 265 token: "", 266 max_parallel: 4, 267 disableReg: false, 268 consistencyMode: "default", 269 }, 270 { 271 name: "check timeout too short", 272 fail: true, 273 consulConfig: map[string]string{ 274 "check_timeout": "99ms", 275 }, 276 }, 277 } 278 279 for _, test := range tests { 280 logger := logging.NewVaultLogger(log.Debug) 281 282 be, err := NewConsulBackend(test.consulConfig, logger) 283 if test.fail { 284 if err == nil { 285 t.Fatalf(`Expected config "%s" to fail`, test.name) 286 } else { 287 continue 288 } 289 } else if !test.fail && err != nil { 290 t.Fatalf("Expected config %s to not fail: %v", test.name, err) 291 } 292 293 c, ok := be.(*ConsulBackend) 294 if !ok { 295 t.Fatalf("Expected ConsulBackend: %s", test.name) 296 } 297 c.disableRegistration = true 298 299 if c.disableRegistration == false { 300 addr := os.Getenv("CONSUL_HTTP_ADDR") 301 if addr == "" { 302 continue 303 } 304 } 305 306 var shutdownCh physical.ShutdownChannel 307 waitGroup := &sync.WaitGroup{} 308 if err := c.RunServiceDiscovery(waitGroup, shutdownCh, test.redirectAddr, testActiveFunc(0.5), testSealedFunc(0.5), testPerformanceStandbyFunc(0.5)); err != nil { 309 t.Fatalf("bad: %v", err) 310 } 311 312 if test.checkTimeout != c.checkTimeout { 313 t.Errorf("bad: %v != %v", test.checkTimeout, c.checkTimeout) 314 } 315 316 if test.path != c.path { 317 t.Errorf("bad: %s %v != %v", test.name, test.path, c.path) 318 } 319 320 if test.service != c.serviceName { 321 t.Errorf("bad: %v != %v", test.service, c.serviceName) 322 } 323 324 if test.consistencyMode != c.consistencyMode { 325 t.Errorf("bad consistency_mode value: %v != %v", test.consistencyMode, c.consistencyMode) 326 } 327 328 // The configuration stored in the Consul "client" object is not exported, so 329 // we either have to skip validating it, or add a method to export it, or use reflection. 330 consulConfig := reflect.Indirect(reflect.ValueOf(c.client)).FieldByName("config") 331 consulConfigScheme := consulConfig.FieldByName("Scheme").String() 332 consulConfigAddress := consulConfig.FieldByName("Address").String() 333 334 if test.scheme != consulConfigScheme { 335 t.Errorf("bad scheme value: %v != %v", test.scheme, consulConfigScheme) 336 } 337 338 if test.address != consulConfigAddress { 339 t.Errorf("bad address value: %v != %v", test.address, consulConfigAddress) 340 } 341 342 // FIXME(sean@): Unable to test max_parallel 343 // if test.max_parallel != cap(c.permitPool) { 344 // t.Errorf("bad: %v != %v", test.max_parallel, cap(c.permitPool)) 345 // } 346 } 347} 348 349func TestConsul_serviceTags(t *testing.T) { 350 tests := []struct { 351 active bool 352 perfStandby bool 353 tags []string 354 }{ 355 { 356 active: true, 357 perfStandby: false, 358 tags: []string{"active"}, 359 }, 360 { 361 active: false, 362 perfStandby: false, 363 tags: []string{"standby"}, 364 }, 365 { 366 active: false, 367 perfStandby: true, 368 tags: []string{"performance-standby"}, 369 }, 370 { 371 active: true, 372 perfStandby: true, 373 tags: []string{"performance-standby"}, 374 }, 375 } 376 377 c := testConsulBackend(t) 378 379 for _, test := range tests { 380 tags := c.fetchServiceTags(test.active, test.perfStandby) 381 if !reflect.DeepEqual(tags[:], test.tags[:]) { 382 t.Errorf("Bad %v: %v %v", test.active, tags, test.tags) 383 } 384 } 385} 386 387func TestConsul_setRedirectAddr(t *testing.T) { 388 tests := []struct { 389 addr string 390 host string 391 port int64 392 pass bool 393 }{ 394 { 395 addr: "http://127.0.0.1:8200/", 396 host: "127.0.0.1", 397 port: 8200, 398 pass: true, 399 }, 400 { 401 addr: "http://127.0.0.1:8200", 402 host: "127.0.0.1", 403 port: 8200, 404 pass: true, 405 }, 406 { 407 addr: "https://127.0.0.1:8200", 408 host: "127.0.0.1", 409 port: 8200, 410 pass: true, 411 }, 412 { 413 addr: "unix:///tmp/.vault.addr.sock", 414 host: "/tmp/.vault.addr.sock", 415 port: -1, 416 pass: true, 417 }, 418 { 419 addr: "127.0.0.1:8200", 420 pass: false, 421 }, 422 { 423 addr: "127.0.0.1", 424 pass: false, 425 }, 426 } 427 for _, test := range tests { 428 c := testConsulBackend(t) 429 err := c.setRedirectAddr(test.addr) 430 if test.pass { 431 if err != nil { 432 t.Fatalf("bad: %v", err) 433 } 434 } else { 435 if err == nil { 436 t.Fatalf("bad, expected fail") 437 } else { 438 continue 439 } 440 } 441 442 if c.redirectHost != test.host { 443 t.Fatalf("bad: %v != %v", c.redirectHost, test.host) 444 } 445 446 if c.redirectPort != test.port { 447 t.Fatalf("bad: %v != %v", c.redirectPort, test.port) 448 } 449 } 450} 451 452func TestConsul_NotifyActiveStateChange(t *testing.T) { 453 c := testConsulBackend(t) 454 455 if err := c.NotifyActiveStateChange(); err != nil { 456 t.Fatalf("bad: %v", err) 457 } 458} 459 460func TestConsul_NotifySealedStateChange(t *testing.T) { 461 c := testConsulBackend(t) 462 463 if err := c.NotifySealedStateChange(); err != nil { 464 t.Fatalf("bad: %v", err) 465 } 466} 467 468func TestConsul_serviceID(t *testing.T) { 469 tests := []struct { 470 name string 471 redirectAddr string 472 serviceName string 473 expected string 474 valid bool 475 }{ 476 { 477 name: "valid host w/o slash", 478 redirectAddr: "http://127.0.0.1:8200", 479 serviceName: "sea-tech-astronomy", 480 expected: "sea-tech-astronomy:127.0.0.1:8200", 481 valid: true, 482 }, 483 { 484 name: "valid host w/ slash", 485 redirectAddr: "http://127.0.0.1:8200/", 486 serviceName: "sea-tech-astronomy", 487 expected: "sea-tech-astronomy:127.0.0.1:8200", 488 valid: true, 489 }, 490 { 491 name: "valid https host w/ slash", 492 redirectAddr: "https://127.0.0.1:8200/", 493 serviceName: "sea-tech-astronomy", 494 expected: "sea-tech-astronomy:127.0.0.1:8200", 495 valid: true, 496 }, 497 { 498 name: "invalid host name", 499 redirectAddr: "https://127.0.0.1:8200/", 500 serviceName: "sea_tech_astronomy", 501 expected: "", 502 valid: false, 503 }, 504 } 505 506 logger := logging.NewVaultLogger(log.Debug) 507 508 for _, test := range tests { 509 be, err := NewConsulBackend(consulConf{ 510 "service": test.serviceName, 511 }, logger) 512 if !test.valid { 513 if err == nil { 514 t.Fatalf("expected an error initializing for name %q", test.serviceName) 515 } 516 continue 517 } 518 if test.valid && err != nil { 519 t.Fatalf("expected Consul to initialize: %v", err) 520 } 521 522 c, ok := be.(*ConsulBackend) 523 if !ok { 524 t.Fatalf("Expected ConsulBackend") 525 } 526 527 if err := c.setRedirectAddr(test.redirectAddr); err != nil { 528 t.Fatalf("bad: %s %v", test.name, err) 529 } 530 531 serviceID := c.serviceID() 532 if serviceID != test.expected { 533 t.Fatalf("bad: %v != %v", serviceID, test.expected) 534 } 535 } 536} 537 538func TestConsulBackend(t *testing.T) { 539 consulToken := os.Getenv("CONSUL_HTTP_TOKEN") 540 addr := os.Getenv("CONSUL_HTTP_ADDR") 541 if addr == "" { 542 cleanup, connURL, token := consul.PrepareTestContainer(t, "1.4.4") 543 defer cleanup() 544 addr, consulToken = connURL, token 545 } 546 547 conf := api.DefaultConfig() 548 conf.Address = addr 549 conf.Token = consulToken 550 client, err := api.NewClient(conf) 551 if err != nil { 552 t.Fatalf("err: %v", err) 553 } 554 555 randPath := fmt.Sprintf("vault-%d/", time.Now().Unix()) 556 defer func() { 557 client.KV().DeleteTree(randPath, nil) 558 }() 559 560 logger := logging.NewVaultLogger(log.Debug) 561 562 b, err := NewConsulBackend(map[string]string{ 563 "address": conf.Address, 564 "path": randPath, 565 "max_parallel": "256", 566 "token": conf.Token, 567 }, logger) 568 if err != nil { 569 t.Fatalf("err: %s", err) 570 } 571 572 physical.ExerciseBackend(t, b) 573 physical.ExerciseBackend_ListPrefix(t, b) 574} 575 576func TestConsul_TooLarge(t *testing.T) { 577 consulToken := os.Getenv("CONSUL_HTTP_TOKEN") 578 addr := os.Getenv("CONSUL_HTTP_ADDR") 579 if addr == "" { 580 cleanup, connURL, token := consul.PrepareTestContainer(t, "1.4.4") 581 defer cleanup() 582 addr, consulToken = connURL, token 583 } 584 585 conf := api.DefaultConfig() 586 conf.Address = addr 587 conf.Token = consulToken 588 client, err := api.NewClient(conf) 589 if err != nil { 590 t.Fatalf("err: %v", err) 591 } 592 593 randPath := fmt.Sprintf("vault-%d/", time.Now().Unix()) 594 defer func() { 595 client.KV().DeleteTree(randPath, nil) 596 }() 597 598 logger := logging.NewVaultLogger(log.Debug) 599 600 b, err := NewConsulBackend(map[string]string{ 601 "address": conf.Address, 602 "path": randPath, 603 "max_parallel": "256", 604 "token": conf.Token, 605 }, logger) 606 if err != nil { 607 t.Fatalf("err: %s", err) 608 } 609 610 zeros := make([]byte, 600000, 600000) 611 n, err := rand.Read(zeros) 612 if n != 600000 { 613 t.Fatalf("expected 500k zeros, read %d", n) 614 } 615 if err != nil { 616 t.Fatal(err) 617 } 618 619 err = b.Put(context.Background(), &physical.Entry{ 620 Key: "foo", 621 Value: zeros, 622 }) 623 if err == nil { 624 t.Fatal("expected error") 625 } 626 if !strings.Contains(err.Error(), physical.ErrValueTooLarge) { 627 t.Fatalf("expected value too large error, got %v", err) 628 } 629 630 err = b.(physical.Transactional).Transaction(context.Background(), []*physical.TxnEntry{ 631 { 632 Operation: physical.PutOperation, 633 Entry: &physical.Entry{ 634 Key: "foo", 635 Value: zeros, 636 }, 637 }, 638 }) 639 if err == nil { 640 t.Fatal("expected error") 641 } 642 if !strings.Contains(err.Error(), physical.ErrValueTooLarge) { 643 t.Fatalf("expected value too large error, got %v", err) 644 } 645} 646 647func TestConsulHABackend(t *testing.T) { 648 consulToken := os.Getenv("CONSUL_HTTP_TOKEN") 649 addr := os.Getenv("CONSUL_HTTP_ADDR") 650 if addr == "" { 651 cleanup, connURL, token := consul.PrepareTestContainer(t, "1.4.4") 652 defer cleanup() 653 addr, consulToken = connURL, token 654 } 655 656 conf := api.DefaultConfig() 657 conf.Address = addr 658 conf.Token = consulToken 659 client, err := api.NewClient(conf) 660 if err != nil { 661 t.Fatalf("err: %v", err) 662 } 663 664 randPath := fmt.Sprintf("vault-%d/", time.Now().Unix()) 665 defer func() { 666 client.KV().DeleteTree(randPath, nil) 667 }() 668 669 logger := logging.NewVaultLogger(log.Debug) 670 config := map[string]string{ 671 "address": conf.Address, 672 "path": randPath, 673 "max_parallel": "-1", 674 "token": conf.Token, 675 } 676 677 b, err := NewConsulBackend(config, logger) 678 if err != nil { 679 t.Fatalf("err: %s", err) 680 } 681 682 b2, err := NewConsulBackend(config, logger) 683 if err != nil { 684 t.Fatalf("err: %s", err) 685 } 686 687 physical.ExerciseHABackend(t, b.(physical.HABackend), b2.(physical.HABackend)) 688 689 detect, ok := b.(physical.RedirectDetect) 690 if !ok { 691 t.Fatalf("consul does not implement RedirectDetect") 692 } 693 host, err := detect.DetectHostAddr() 694 if err != nil { 695 t.Fatalf("err: %s", err) 696 } 697 if host == "" { 698 t.Fatalf("bad addr: %v", host) 699 } 700} 701