| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| //go:build !race |
| |
| package command |
| |
| import ( |
| "context" |
| "fmt" |
| "io/ioutil" |
| "os" |
| "strings" |
| "testing" |
| |
| "github.com/hashicorp/vault/vault/diagnose" |
| "github.com/mitchellh/cli" |
| ) |
| |
| func testOperatorDiagnoseCommand(tb testing.TB) *OperatorDiagnoseCommand { |
| tb.Helper() |
| |
| ui := cli.NewMockUi() |
| return &OperatorDiagnoseCommand{ |
| diagnose: diagnose.New(ioutil.Discard), |
| BaseCommand: &BaseCommand{ |
| UI: ui, |
| }, |
| skipEndEnd: true, |
| } |
| } |
| |
| func TestOperatorDiagnoseCommand_Run(t *testing.T) { |
| t.Parallel() |
| cases := []struct { |
| name string |
| args []string |
| expected []*diagnose.Result |
| }{ |
| { |
| "diagnose_ok", |
| []string{ |
| "-config", "./server/test-fixtures/config_diagnose_ok.hcl", |
| }, |
| []*diagnose.Result{ |
| { |
| Name: "Parse Configuration", |
| Status: diagnose.OkStatus, |
| }, |
| { |
| Name: "Start Listeners", |
| Status: diagnose.WarningStatus, |
| Children: []*diagnose.Result{ |
| { |
| Name: "Create Listeners", |
| Status: diagnose.OkStatus, |
| }, |
| { |
| Name: "Check Listener TLS", |
| Status: diagnose.WarningStatus, |
| Warnings: []string{ |
| "TLS is disabled in a listener config stanza.", |
| }, |
| }, |
| }, |
| }, |
| { |
| Name: "Check Storage", |
| Status: diagnose.OkStatus, |
| Children: []*diagnose.Result{ |
| { |
| Name: "Create Storage Backend", |
| Status: diagnose.OkStatus, |
| }, |
| { |
| Name: "Check Consul TLS", |
| Status: diagnose.SkippedStatus, |
| }, |
| { |
| Name: "Check Consul Direct Storage Access", |
| Status: diagnose.OkStatus, |
| }, |
| }, |
| }, |
| { |
| Name: "Check Service Discovery", |
| Status: diagnose.OkStatus, |
| Children: []*diagnose.Result{ |
| { |
| Name: "Check Consul Service Discovery TLS", |
| Status: diagnose.SkippedStatus, |
| }, |
| { |
| Name: "Check Consul Direct Service Discovery", |
| Status: diagnose.OkStatus, |
| }, |
| }, |
| }, |
| { |
| Name: "Create Vault Server Configuration Seals", |
| Status: diagnose.OkStatus, |
| }, |
| { |
| Name: "Create Core Configuration", |
| Status: diagnose.OkStatus, |
| Children: []*diagnose.Result{ |
| { |
| Name: "Initialize Randomness for Core", |
| Status: diagnose.OkStatus, |
| }, |
| }, |
| }, |
| { |
| Name: "HA Storage", |
| Status: diagnose.OkStatus, |
| Children: []*diagnose.Result{ |
| { |
| Name: "Create HA Storage Backend", |
| Status: diagnose.OkStatus, |
| }, |
| { |
| Name: "Check HA Consul Direct Storage Access", |
| Status: diagnose.OkStatus, |
| }, |
| { |
| Name: "Check Consul TLS", |
| Status: diagnose.SkippedStatus, |
| }, |
| }, |
| }, |
| { |
| Name: "Determine Redirect Address", |
| Status: diagnose.OkStatus, |
| }, |
| { |
| Name: "Check Cluster Address", |
| Status: diagnose.OkStatus, |
| }, |
| { |
| Name: "Check Core Creation", |
| Status: diagnose.OkStatus, |
| }, |
| { |
| Name: "Start Listeners", |
| Status: diagnose.WarningStatus, |
| Children: []*diagnose.Result{ |
| { |
| Name: "Create Listeners", |
| Status: diagnose.OkStatus, |
| }, |
| { |
| Name: "Check Listener TLS", |
| Status: diagnose.WarningStatus, |
| Warnings: []string{ |
| "TLS is disabled in a listener config stanza.", |
| }, |
| }, |
| }, |
| }, |
| { |
| Name: "Check Autounseal Encryption", |
| Status: diagnose.SkippedStatus, |
| Message: "Skipping barrier encryption", |
| }, |
| { |
| Name: "Check Server Before Runtime", |
| Status: diagnose.OkStatus, |
| }, |
| { |
| Name: "Finalize Shamir Seal", |
| Status: diagnose.OkStatus, |
| }, |
| }, |
| }, |
| { |
| "diagnose_raft_problems", |
| []string{ |
| "-config", "./server/test-fixtures/config_raft.hcl", |
| }, |
| []*diagnose.Result{ |
| { |
| Name: "Check Storage", |
| Status: diagnose.WarningStatus, |
| Children: []*diagnose.Result{ |
| { |
| Name: "Create Storage Backend", |
| Status: diagnose.OkStatus, |
| }, |
| { |
| Name: "Check Raft Folder Permissions", |
| Status: diagnose.WarningStatus, |
| Message: "too many permissions", |
| }, |
| { |
| Name: "Check For Raft Quorum", |
| Status: diagnose.WarningStatus, |
| Message: "0 voters found", |
| }, |
| }, |
| }, |
| }, |
| }, |
| { |
| "diagnose_invalid_storage", |
| []string{ |
| "-config", "./server/test-fixtures/nostore_config.hcl", |
| }, |
| []*diagnose.Result{ |
| { |
| Name: "Check Storage", |
| Status: diagnose.ErrorStatus, |
| Message: "No storage stanza in Vault server configuration.", |
| }, |
| }, |
| }, |
| { |
| "diagnose_listener_config_ok", |
| []string{ |
| "-config", "./server/test-fixtures/tls_config_ok.hcl", |
| }, |
| []*diagnose.Result{ |
| { |
| Name: "Start Listeners", |
| Status: diagnose.OkStatus, |
| Children: []*diagnose.Result{ |
| { |
| Name: "Create Listeners", |
| Status: diagnose.OkStatus, |
| }, |
| { |
| Name: "Check Listener TLS", |
| Status: diagnose.OkStatus, |
| }, |
| }, |
| }, |
| }, |
| }, |
| { |
| "diagnose_invalid_https_storage", |
| []string{ |
| "-config", "./server/test-fixtures/config_bad_https_storage.hcl", |
| }, |
| []*diagnose.Result{ |
| { |
| Name: "Check Storage", |
| Status: diagnose.ErrorStatus, |
| Children: []*diagnose.Result{ |
| { |
| Name: "Create Storage Backend", |
| Status: diagnose.OkStatus, |
| }, |
| { |
| Name: "Check Consul TLS", |
| Status: diagnose.ErrorStatus, |
| Message: "certificate has expired or is not yet valid", |
| Warnings: []string{ |
| "expired or near expiry", |
| }, |
| }, |
| { |
| Name: "Check Consul Direct Storage Access", |
| Status: diagnose.OkStatus, |
| }, |
| }, |
| }, |
| }, |
| }, |
| { |
| "diagnose_invalid_https_hastorage", |
| []string{ |
| "-config", "./server/test-fixtures/config_diagnose_hastorage_bad_https.hcl", |
| }, |
| []*diagnose.Result{ |
| { |
| Name: "Check Storage", |
| Status: diagnose.WarningStatus, |
| Children: []*diagnose.Result{ |
| { |
| Name: "Create Storage Backend", |
| Status: diagnose.OkStatus, |
| }, |
| { |
| Name: "Check Consul TLS", |
| Status: diagnose.SkippedStatus, |
| }, |
| { |
| Name: "Check Consul Direct Storage Access", |
| Status: diagnose.WarningStatus, |
| Advice: "We recommend connecting to a local agent.", |
| Warnings: []string{ |
| "Vault storage is directly connected to a Consul server.", |
| }, |
| }, |
| }, |
| }, |
| { |
| Name: "HA Storage", |
| Status: diagnose.ErrorStatus, |
| Children: []*diagnose.Result{ |
| { |
| Name: "Create HA Storage Backend", |
| Status: diagnose.OkStatus, |
| }, |
| { |
| Name: "Check HA Consul Direct Storage Access", |
| Status: diagnose.WarningStatus, |
| Advice: "We recommend connecting to a local agent.", |
| Warnings: []string{ |
| "Vault storage is directly connected to a Consul server.", |
| }, |
| }, |
| { |
| Name: "Check Consul TLS", |
| Status: diagnose.ErrorStatus, |
| Message: "certificate has expired or is not yet valid", |
| Warnings: []string{ |
| "expired or near expiry", |
| }, |
| }, |
| }, |
| }, |
| { |
| Name: "Check Cluster Address", |
| Status: diagnose.ErrorStatus, |
| }, |
| }, |
| }, |
| { |
| "diagnose_seal_transit_tls_check_fail", |
| []string{ |
| "-config", "./server/test-fixtures/diagnose_seal_transit_tls_check.hcl", |
| }, |
| []*diagnose.Result{ |
| { |
| Name: "Check Transit Seal TLS", |
| Status: diagnose.WarningStatus, |
| Warnings: []string{ |
| "Found at least one intermediate certificate in the CA certificate file.", |
| }, |
| }, |
| }, |
| }, |
| { |
| "diagnose_invalid_https_sr", |
| []string{ |
| "-config", "./server/test-fixtures/diagnose_bad_https_consul_sr.hcl", |
| }, |
| []*diagnose.Result{ |
| { |
| Name: "Check Service Discovery", |
| Status: diagnose.ErrorStatus, |
| Children: []*diagnose.Result{ |
| { |
| Name: "Check Consul Service Discovery TLS", |
| Status: diagnose.ErrorStatus, |
| Message: "certificate has expired or is not yet valid", |
| Warnings: []string{ |
| "expired or near expiry", |
| }, |
| }, |
| { |
| Name: "Check Consul Direct Service Discovery", |
| Status: diagnose.WarningStatus, |
| Warnings: []string{ |
| diagnose.DirAccessErr, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| { |
| "diagnose_direct_storage_access", |
| []string{ |
| "-config", "./server/test-fixtures/diagnose_ok_storage_direct_access.hcl", |
| }, |
| []*diagnose.Result{ |
| { |
| Name: "Check Storage", |
| Status: diagnose.WarningStatus, |
| Children: []*diagnose.Result{ |
| { |
| Name: "Create Storage Backend", |
| Status: diagnose.OkStatus, |
| }, |
| { |
| Name: "Check Consul TLS", |
| Status: diagnose.SkippedStatus, |
| }, |
| { |
| Name: "Check Consul Direct Storage Access", |
| Status: diagnose.WarningStatus, |
| Warnings: []string{ |
| diagnose.DirAccessErr, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| { |
| "diagnose_raft_no_folder_backend", |
| []string{ |
| "-config", "./server/test-fixtures/diagnose_raft_no_bolt_folder.hcl", |
| }, |
| []*diagnose.Result{ |
| { |
| Name: "Check Storage", |
| Status: diagnose.ErrorStatus, |
| Message: "Diagnose could not initialize storage backend.", |
| Children: []*diagnose.Result{ |
| { |
| Name: "Create Storage Backend", |
| Status: diagnose.ErrorStatus, |
| Message: "no such file or directory", |
| }, |
| }, |
| }, |
| }, |
| }, |
| { |
| "diagnose_telemetry_partial_circonus", |
| []string{ |
| "-config", "./server/test-fixtures/diagnose_bad_telemetry1.hcl", |
| }, |
| []*diagnose.Result{ |
| { |
| Name: "Check Telemetry", |
| Status: diagnose.ErrorStatus, |
| Message: "incomplete Circonus telemetry configuration, missing circonus_api_url", |
| }, |
| }, |
| }, |
| { |
| "diagnose_telemetry_partial_dogstats", |
| []string{ |
| "-config", "./server/test-fixtures/diagnose_bad_telemetry2.hcl", |
| }, |
| []*diagnose.Result{ |
| { |
| Name: "Check Telemetry", |
| Status: diagnose.ErrorStatus, |
| Message: "incomplete DogStatsD telemetry configuration, missing dogstatsd_addr, while dogstatsd_tags specified", |
| }, |
| }, |
| }, |
| { |
| "diagnose_telemetry_partial_stackdriver", |
| []string{ |
| "-config", "./server/test-fixtures/diagnose_bad_telemetry3.hcl", |
| }, |
| []*diagnose.Result{ |
| { |
| Name: "Check Telemetry", |
| Status: diagnose.ErrorStatus, |
| Message: "incomplete Stackdriver telemetry configuration, missing stackdriver_project_id", |
| }, |
| }, |
| }, |
| { |
| "diagnose_telemetry_default", |
| []string{ |
| "-config", "./server/test-fixtures/config4.hcl", |
| }, |
| []*diagnose.Result{ |
| { |
| Name: "Check Telemetry", |
| Status: diagnose.WarningStatus, |
| Warnings: []string{"Telemetry is using default configuration"}, |
| }, |
| }, |
| }, |
| } |
| |
| t.Run("validations", func(t *testing.T) { |
| t.Parallel() |
| |
| for _, tc := range cases { |
| tc := tc |
| t.Run(tc.name, func(t *testing.T) { |
| t.Parallel() |
| client, closer := testVaultServer(t) |
| defer closer() |
| cmd := testOperatorDiagnoseCommand(t) |
| cmd.client = client |
| |
| cmd.Run(tc.args) |
| result := cmd.diagnose.Finalize(context.Background()) |
| |
| if err := compareResults(tc.expected, result.Children); err != nil { |
| t.Fatalf("Did not find expected test results: %v", err) |
| } |
| }) |
| } |
| }) |
| } |
| |
| func compareResults(expected []*diagnose.Result, actual []*diagnose.Result) error { |
| for _, exp := range expected { |
| found := false |
| // Check them all so we don't have to be order specific |
| for _, act := range actual { |
| fmt.Printf("%+v", act) |
| if exp.Name == act.Name { |
| found = true |
| if err := compareResult(exp, act); err != nil { |
| return err |
| } |
| break |
| } |
| } |
| if !found { |
| return fmt.Errorf("could not find expected test result: %s", exp.Name) |
| } |
| } |
| return nil |
| } |
| |
| func compareResult(exp *diagnose.Result, act *diagnose.Result) error { |
| if exp.Name != act.Name { |
| return fmt.Errorf("names mismatch: %s vs %s", exp.Name, act.Name) |
| } |
| if exp.Status != act.Status { |
| if act.Status != diagnose.OkStatus { |
| return fmt.Errorf("section %s, status mismatch: %s vs %s, got error %s", exp.Name, exp.Status, act.Status, act.Message) |
| } |
| return fmt.Errorf("section %s, status mismatch: %s vs %s", exp.Name, exp.Status, act.Status) |
| } |
| if exp.Message != "" && exp.Message != act.Message && !strings.Contains(act.Message, exp.Message) { |
| return fmt.Errorf("section %s, message not found: %s in %s", exp.Name, exp.Message, act.Message) |
| } |
| if exp.Advice != "" && exp.Advice != act.Advice && !strings.Contains(act.Advice, exp.Advice) { |
| return fmt.Errorf("section %s, advice not found: %s in %s", exp.Name, exp.Advice, act.Advice) |
| } |
| if len(exp.Warnings) != len(act.Warnings) { |
| return fmt.Errorf("section %s, warning count mismatch: %d vs %d", exp.Name, len(exp.Warnings), len(act.Warnings)) |
| } |
| for j := range exp.Warnings { |
| if !strings.Contains(act.Warnings[j], exp.Warnings[j]) { |
| return fmt.Errorf("section %s, warning message not found: %s in %s", exp.Name, exp.Warnings[j], act.Warnings[j]) |
| } |
| } |
| if len(exp.Children) > len(act.Children) { |
| errStrings := []string{} |
| for _, c := range act.Children { |
| errStrings = append(errStrings, fmt.Sprintf("%+v", c)) |
| } |
| return fmt.Errorf(strings.Join(errStrings, ",")) |
| } |
| |
| if len(exp.Children) > 0 { |
| return compareResults(exp.Children, act.Children) |
| } |
| |
| // Remove raft file if it exists |
| os.Remove("./server/test-fixtures/vault.db") |
| os.RemoveAll("./server/test-fixtures/raft") |
| |
| return nil |
| } |