Files
vault/physical/raft/config_test.go
Paul Banks fc0abf2d9f Remove CE-only warning from shared tests (#26052)
* Remove CE-only warning from shared tests

* Add tests for all warnings emitted during raft config parsing

* Unmark warnings as CE only that are universal
2024-03-20 17:08:33 +00:00

431 lines
12 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package raft
import (
"bytes"
"testing"
"time"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/helper/constants"
"github.com/stretchr/testify/require"
)
func ceOnlyWarnings(warns ...string) []string {
if !constants.IsEnterprise {
return warns
}
return nil
}
func TestRaft_ParseConfig(t *testing.T) {
// Note some of these can be parallel tests but since we need to setEnv in
// some we can't make them all parallel so it's don inside the loop. We assume
// if a case doesn't set anything on the Env it's safe to run in parallel.
tcs := []struct {
name string
conf map[string]string
env map[string]string
wantMutation func(cfg *RaftBackendConfig)
wantErr string
wantWarns []string
}{
// RAFT WAL --------------------------------------------------------------
{
name: "WAL backend junk",
conf: map[string]string{
"raft_wal": "notabooleanlol",
},
wantErr: "does not parse as a boolean",
},
{
name: "WAL verifier junk",
conf: map[string]string{
"raft_wal": "true",
"raft_log_verifier_enabled": "notabooleanlol",
},
wantErr: "does not parse as a boolean",
},
{
name: "WAL verifier interval, zero",
conf: map[string]string{
"raft_log_verifier_enabled": "true",
"raft_log_verification_interval": "0s",
},
wantMutation: func(cfg *RaftBackendConfig) {
cfg.RaftLogVerifierEnabled = true
cfg.RaftLogVerificationInterval = defaultRaftLogVerificationInterval
},
wantWarns: []string{"raft_log_verification_interval is less than the minimum allowed"},
},
{
name: "WAL verifier interval, one",
conf: map[string]string{
"raft_log_verifier_enabled": "true",
"raft_log_verification_interval": "0s",
},
wantMutation: func(cfg *RaftBackendConfig) {
cfg.RaftLogVerifierEnabled = true
// Below min so should get default
cfg.RaftLogVerificationInterval = defaultRaftLogVerificationInterval
},
wantWarns: []string{"raft_log_verification_interval is less than the minimum allowed"},
},
{
name: "WAL verifier interval, nothing",
conf: map[string]string{
"raft_log_verifier_enabled": "true",
"raft_log_verification_interval": "",
},
wantMutation: func(cfg *RaftBackendConfig) {
cfg.RaftLogVerifierEnabled = true
cfg.RaftLogVerificationInterval = defaultRaftLogVerificationInterval
},
wantWarns: []string{"raft_log_verification_interval is less than the minimum allowed"},
},
{
name: "WAL verifier interval, valid",
conf: map[string]string{
"raft_log_verifier_enabled": "true",
"raft_log_verification_interval": "75s",
},
wantMutation: func(cfg *RaftBackendConfig) {
cfg.RaftLogVerifierEnabled = true
cfg.RaftLogVerificationInterval = 75 * time.Second
},
},
{
name: "WAL verifier interval, junk",
conf: map[string]string{
"raft_log_verifier_enabled": "true",
"raft_log_verification_interval": "notaduration",
},
wantErr: "does not parse as a duration",
},
// AUTOPILOT Upgrades ----------------------------------------------------
{
name: "Autopilot upgrade version, junk",
conf: map[string]string{
"autopilot_upgrade_version": "hahano",
},
wantErr: "does not parse",
},
// AUTOPILOT Redundancy Zone ---------------------------------------------
{
name: "Autopilot redundancy zone, ok",
conf: map[string]string{
"autopilot_redundancy_zone": "us-east-1a",
},
wantMutation: func(cfg *RaftBackendConfig) {
cfg.AutopilotRedundancyZone = "us-east-1a"
},
wantWarns: ceOnlyWarnings("configuration for a Vault Enterprise feature has been ignored: field=autopilot_redundancy_zone"),
},
// Non-voter config ------------------------------------------------------
{
name: "non-voter, no retry-join, valid false",
conf: map[string]string{
raftNonVoterConfigKey: "false",
},
wantMutation: func(cfg *RaftBackendConfig) {
// Should be default
},
},
{
name: "non-voter, retry-join, valid false",
conf: map[string]string{
"retry_join": "not-empty",
raftNonVoterConfigKey: "false",
},
wantMutation: func(cfg *RaftBackendConfig) {
cfg.RetryJoin = "not-empty"
},
},
{
name: "non-voter, no retry-join, valid true",
conf: map[string]string{
raftNonVoterConfigKey: "true",
},
wantErr: "only valid if at least one retry_join stanza is specified",
},
{
name: "non-voter, retry-join, valid true",
conf: map[string]string{
"retry_join": "not-empty",
raftNonVoterConfigKey: "true",
},
wantMutation: func(cfg *RaftBackendConfig) {
cfg.RetryJoin = "not-empty"
cfg.RaftNonVoter = true
},
wantWarns: ceOnlyWarnings("configuration for a Vault Enterprise feature has been ignored: field=retry_join_as_non_voter"),
},
{
name: "non-voter, no retry-join, invalid empty",
conf: map[string]string{
raftNonVoterConfigKey: "",
},
wantErr: "failed to parse retry_join_as_non_voter",
},
{
name: "non-voter, retry-join, invalid empty",
conf: map[string]string{
"retry_join": "not-empty",
raftNonVoterConfigKey: "",
},
wantErr: "failed to parse retry_join_as_non_voter",
},
{
name: "non-voter, no retry-join, invalid truthy",
conf: map[string]string{
raftNonVoterConfigKey: "no",
},
wantErr: "failed to parse retry_join_as_non_voter",
},
{
name: "non-voter, retry-join, invalid truthy",
conf: map[string]string{
"retry_join": "not-empty",
raftNonVoterConfigKey: "no",
},
wantErr: "failed to parse retry_join_as_non_voter",
},
{
name: "non-voter, no retry-join, invalid",
conf: map[string]string{
raftNonVoterConfigKey: "totallywrong",
},
wantErr: "failed to parse retry_join_as_non_voter",
},
{
name: "non-voter, retry-join, invalid",
conf: map[string]string{
"retry_join": "not-empty",
raftNonVoterConfigKey: "totallywrong",
},
wantErr: "failed to parse retry_join_as_non_voter",
},
{
// Note for historical reasons we treat any non-empty value as true in ENV
// vars.
name: "non-voter, no retry-join, valid env false",
env: map[string]string{
EnvVaultRaftNonVoter: "false",
},
wantErr: "only valid if at least one retry_join stanza is specified",
},
{
name: "non-voter, retry-join, valid env false",
env: map[string]string{
EnvVaultRaftNonVoter: "false",
},
conf: map[string]string{
"retry_join": "not-empty",
},
wantMutation: func(cfg *RaftBackendConfig) {
cfg.RetryJoin = "not-empty"
cfg.RaftNonVoter = true // Any non-empty value is true
},
wantWarns: ceOnlyWarnings("configuration for a Vault Enterprise feature has been ignored: field=retry_join_as_non_voter"),
},
{
name: "non-voter, no retry-join, valid env true",
env: map[string]string{
EnvVaultRaftNonVoter: "true",
},
wantErr: "only valid if at least one retry_join stanza is specified",
},
{
name: "non-voter, retry-join, valid env true",
env: map[string]string{
EnvVaultRaftNonVoter: "true",
},
conf: map[string]string{
"retry_join": "not-empty",
},
wantMutation: func(cfg *RaftBackendConfig) {
cfg.RetryJoin = "not-empty"
cfg.RaftNonVoter = true
},
wantWarns: ceOnlyWarnings("configuration for a Vault Enterprise feature has been ignored: field=retry_join_as_non_voter"),
},
{
name: "non-voter, no retry-join, valid env not-boolean",
env: map[string]string{
EnvVaultRaftNonVoter: "anything",
},
wantErr: "only valid if at least one retry_join stanza is specified",
},
{
name: "non-voter, retry-join, valid env not-boolean",
env: map[string]string{
EnvVaultRaftNonVoter: "anything",
},
conf: map[string]string{
"retry_join": "not-empty",
},
wantMutation: func(cfg *RaftBackendConfig) {
cfg.RetryJoin = "not-empty"
cfg.RaftNonVoter = true
},
wantWarns: ceOnlyWarnings("configuration for a Vault Enterprise feature has been ignored: field=retry_join_as_non_voter"),
},
{
name: "non-voter, no retry-join, valid env empty",
env: map[string]string{
EnvVaultRaftNonVoter: "",
},
wantMutation: func(cfg *RaftBackendConfig) {
// Default
},
},
{
name: "non-voter, retry-join, valid env empty",
env: map[string]string{
EnvVaultRaftNonVoter: "",
},
conf: map[string]string{
"retry_join": "not-empty",
},
wantMutation: func(cfg *RaftBackendConfig) {
cfg.RetryJoin = "not-empty"
},
},
{
name: "non-voter, no retry-join, both set env preferred",
env: map[string]string{
EnvVaultRaftNonVoter: "true",
},
conf: map[string]string{
raftNonVoterConfigKey: "false",
},
wantErr: "only valid if at least one retry_join stanza is specified",
},
{
name: "non-voter, retry-join, both set env preferred",
env: map[string]string{
EnvVaultRaftNonVoter: "true",
},
conf: map[string]string{
"retry_join": "not-empty",
raftNonVoterConfigKey: "false",
},
wantMutation: func(cfg *RaftBackendConfig) {
cfg.RetryJoin = "not-empty"
cfg.RaftNonVoter = true // Env should win
},
wantWarns: ceOnlyWarnings("configuration for a Vault Enterprise feature has been ignored: field=retry_join_as_non_voter"),
},
// Entry Size Limits -----------------------------------------------------
{
name: "entry size, happy path",
conf: map[string]string{
"max_entry_size": "123456",
"max_mount_and_namespace_table_entry_size": "654321",
},
wantMutation: func(cfg *RaftBackendConfig) {
cfg.MaxEntrySize = 123456
cfg.MaxMountAndNamespaceTableEntrySize = 654321
},
wantWarns: ceOnlyWarnings("configuration for a Vault Enterprise feature has been ignored: field=max_mount_and_namespace_table_entry_size"),
},
{
name: "entry size, junk entry size",
conf: map[string]string{
"max_entry_size": "sadfsaf",
"max_mount_and_namespace_table_entry_size": "654321",
},
wantErr: "failed to parse 'max_entry_size'",
},
{
name: "entry size, junk mount entry size",
conf: map[string]string{
"max_entry_size": "123456",
"max_mount_and_namespace_table_entry_size": "1MiB",
},
wantErr: "failed to parse 'max_mount_and_namespace_table_entry_size'",
},
{
name: "entry size, way too small mount entry size",
conf: map[string]string{
"max_mount_and_namespace_table_entry_size": "1",
},
wantErr: "'max_mount_and_namespace_table_entry_size' must be at least 1024 bytes",
},
{
name: "entry size, way too big mount entry size",
conf: map[string]string{
"max_mount_and_namespace_table_entry_size": "20000000",
},
wantErr: "'max_mount_and_namespace_table_entry_size' must be at most 10,485,760 bytes (10MiB)",
},
}
// Set a nodeid and path to remove noise from all the test cases.
baseConf := map[string]string{
"node_id": "abc123",
"path": "/dummy/path",
}
for _, tc := range tcs {
tc := tc
t.Run(tc.name, func(t *testing.T) {
if len(tc.env) == 0 {
// Only run in parallel if there are no env vars to set.
t.Parallel()
}
var logs bytes.Buffer
logger := hclog.New(&hclog.LoggerOptions{
Level: hclog.Warn,
Output: &logs,
})
if tc.conf == nil {
tc.conf = make(map[string]string)
}
for k, v := range baseConf {
if _, ok := tc.conf[k]; !ok {
tc.conf[k] = v
}
}
// Make a default-valued config to compare against later. Note we do this
// before setting ENV as that would could change behavior!
wantCfg, err := parseRaftBackendConfig(baseConf, hclog.NewNullLogger())
require.NoError(t, err)
for k, v := range tc.env {
t.Setenv(k, v)
}
cfg, err := parseRaftBackendConfig(tc.conf, logger)
if tc.wantErr != "" {
require.Error(t, err)
require.Contains(t, err.Error(), tc.wantErr)
return
}
tc.wantMutation(wantCfg)
require.Equal(t, wantCfg, cfg)
allLogs := logs.String()
for _, warn := range tc.wantWarns {
require.Contains(t, allLogs, warn)
}
if len(tc.wantWarns) == 0 {
require.NotContains(t, allLogs, "[WARN]", "no warnings expected")
}
})
}
}