Files
threshold/internal/cmd/config/config_test.go
T
Timothy Messier fd5d15af27 feat(config): Add database config options for idle connection management (#2176)
This adds two new config options for the `database` stanza:

- `max_idle_connections`
- `max_idle_time`

These correspond to settings for the underlying `sql.DB` instance. They
can be used to have finer control over the controller's database
connection pool. For example this can be used to mitigate the high
connection overhead when connecting to postgres by setting a high
`max_idle_connections` and either setting `max_idle_time` to zero or to
a high duration.

Alternatively, if using a connection pool like pgpool-II or pgbouncer,
`max_idle_connections` can be set to 0.

See:
    https://pkg.go.dev/database/sql#DB.SetMaxIdleConns
    https://pkg.go.dev/database/sql#DB.SetConnMaxIdleTime
    https://www.pgpool.net/mediawiki/index.php/Main_Page
    https://www.pgbouncer.org/
2022-07-05 15:25:55 -04:00

1647 lines
38 KiB
Go

package config
import (
"fmt"
"os"
"testing"
"time"
"github.com/hashicorp/boundary/internal/observability/event"
configutil "github.com/hashicorp/go-secure-stdlib/configutil/v2"
"github.com/hashicorp/go-secure-stdlib/listenerutil"
"github.com/hashicorp/go-secure-stdlib/parseutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDevController(t *testing.T) {
actual, err := DevController()
if err != nil {
t.Fatal(err)
}
truePointer := new(bool)
*truePointer = true
exp := &Config{
Eventing: event.DefaultEventerConfig(),
SharedConfig: &configutil.SharedConfig{
DisableMlock: true,
Listeners: []*listenerutil.ListenerConfig{
{
Type: "tcp",
Purpose: []string{"api"},
TLSDisable: true,
CorsEnabled: truePointer,
CorsAllowedOrigins: []string{"*"},
},
{
Type: "tcp",
Purpose: []string{"cluster"},
},
{
Type: "tcp",
Purpose: []string{"ops"},
TLSDisable: true,
},
},
Seals: []*configutil.KMS{
{
Type: "aead",
Purpose: []string{"root"},
Config: map[string]string{
"aead_type": "aes-gcm",
"key_id": "global_root",
},
},
{
Type: "aead",
Purpose: []string{"worker-auth"},
Config: map[string]string{
"aead_type": "aes-gcm",
"key_id": "global_worker-auth",
},
},
{
Type: "aead",
Purpose: []string{"worker-auth-storage"},
Config: map[string]string{
"aead_type": "aes-gcm",
"key_id": "global_worker-auth-storage",
},
},
{
Type: "aead",
Purpose: []string{"recovery"},
Config: map[string]string{
"aead_type": "aes-gcm",
"key_id": "global_recovery",
},
},
},
},
Controller: &Controller{
Name: "dev-controller",
Description: "A default controller created in dev mode",
},
DevController: true,
}
exp.Listeners[0].RawConfig = actual.Listeners[0].RawConfig
exp.Listeners[1].RawConfig = actual.Listeners[1].RawConfig
exp.Listeners[2].RawConfig = actual.Listeners[2].RawConfig
exp.Seals[0].Config["key"] = actual.Seals[0].Config["key"]
exp.Seals[1].Config["key"] = actual.Seals[1].Config["key"]
exp.Seals[2].Config["key"] = actual.Seals[2].Config["key"]
exp.Seals[3].Config["key"] = actual.Seals[3].Config["key"]
exp.DevControllerKey = actual.Seals[0].Config["key"]
exp.DevWorkerAuthKey = actual.Seals[1].Config["key"]
exp.DevWorkerAuthStorageKey = actual.Seals[2].Config["key"]
exp.DevRecoveryKey = actual.Seals[3].Config["key"]
assert.Equal(t, exp, actual)
// Do some CORS-specific testing
{
// CORS disabled
conf := `
listener "tcp" {
purpose = "api"
cors_enabled = false
}
`
actual, err = Parse(conf)
assert.NoError(t, err)
l0 := actual.Listeners[0]
assert.False(t, *l0.CorsEnabled)
assert.Empty(t, l0.CorsAllowedHeaders)
// Enabled with a wildcard
conf = `
listener "tcp" {
purpose = "api"
cors_enabled = true
cors_allowed_origins = ["*"]
}
`
actual, err = Parse(conf)
assert.NoError(t, err)
l0 = actual.Listeners[0]
assert.True(t, *l0.CorsEnabled)
assert.Equal(t, []string{"*"}, l0.CorsAllowedOrigins)
assert.Nil(t, l0.CorsDisableDefaultAllowedOriginValues)
// Disabled, default behavior
conf = `
listener "tcp" {
purpose = "api"
}
`
actual, err = Parse(conf)
assert.NoError(t, err)
l0 = actual.Listeners[0]
assert.True(t, *l0.CorsEnabled)
assert.Equal(t, []string{"*"}, l0.CorsAllowedOrigins)
assert.Nil(t, l0.CorsDisableDefaultAllowedOriginValues)
// Disabled, default behavior
conf = `
listener "tcp" {
purpose = "api"
cors_disable_default_allowed_origin_values = true
}
`
actual, err = Parse(conf)
assert.NoError(t, err)
l0 = actual.Listeners[0]
assert.Nil(t, l0.CorsEnabled)
assert.Empty(t, l0.CorsAllowedOrigins)
assert.True(t, *l0.CorsDisableDefaultAllowedOriginValues)
}
// Test plugins block
{
// CORS disabled
conf := `
plugins {
execution_dir = "/tmp/foobar"
}
`
actual, err = Parse(conf)
assert.NoError(t, err)
assert.Equal(t, actual.Plugins.ExecutionDir, "/tmp/foobar")
}
}
func TestDevWorker(t *testing.T) {
actual, err := DevWorker()
if err != nil {
t.Fatal(err)
}
exp := &Config{
Eventing: event.DefaultEventerConfig(),
SharedConfig: &configutil.SharedConfig{
DisableMlock: true,
Listeners: []*listenerutil.ListenerConfig{
{
Type: "tcp",
Purpose: []string{"proxy"},
},
},
},
Worker: &Worker{
Name: "w_1234567890",
Description: "A default worker created in dev mode",
InitialUpstreams: []string{"127.0.0.1"},
InitialUpstreamsRaw: []interface{}{"127.0.0.1"},
Tags: map[string][]string{
"type": {"dev", "local"},
},
},
}
exp.Listeners[0].RawConfig = actual.Listeners[0].RawConfig
exp.Worker.TagsRaw = actual.Worker.TagsRaw
assert.Equal(t, exp, actual)
// Redo it with key=value syntax for tags
devWorkerKeyValueConfig := `
listener "tcp" {
purpose = "proxy"
}
worker {
name = "w_1234567890"
description = "A default worker created in dev mode"
initial_upstreams = ["127.0.0.1"]
tags = ["type=dev", "type=local"]
}
`
actual, err = Parse(devConfig + devWorkerKeyValueConfig)
assert.NoError(t, err)
exp.Listeners[0].RawConfig = actual.Listeners[0].RawConfig
exp.Worker.TagsRaw = actual.Worker.TagsRaw
assert.Equal(t, exp, actual)
// Handle when there is a singular value not indicated as a slice
devWorkerKeyValueConfig = `
listener "tcp" {
purpose = "proxy"
}
worker {
name = "w_1234567890"
description = "A default worker created in dev mode"
initial_upstreams = ["127.0.0.1"]
tags {
type = "local"
}
}
`
actual, err = Parse(devConfig + devWorkerKeyValueConfig)
assert.NoError(t, err)
exp.Listeners[0].RawConfig = actual.Listeners[0].RawConfig
exp.Worker.TagsRaw = actual.Worker.TagsRaw
prevTags := exp.Worker.Tags
exp.Worker.Tags = map[string][]string{"type": {"local"}}
assert.Equal(t, exp, actual)
exp.Worker.Tags = prevTags
// Redo it with non-lower-cased keys
devWorkerKeyValueConfig = `
listener "tcp" {
purpose = "proxy"
}
worker {
name = "w_1234567890"
description = "A default worker created in dev mode"
initial_upstreams = ["127.0.0.1"]
tags = ["tyPe=dev", "type=local"]
}
`
_, err = Parse(devConfig + devWorkerKeyValueConfig)
assert.Error(t, err)
// Redo it with non-lower-cased values
devWorkerKeyValueConfig = `
listener "tcp" {
purpose = "proxy"
}
worker {
name = "w_1234567890"
description = "A default worker created in dev mode"
initial_upstreams = ["127.0.0.1"]
tags = ["type=dev", "type=loCal"]
}
`
_, err = Parse(devConfig + devWorkerKeyValueConfig)
assert.Error(t, err)
// Redo with non-printable characters to validate the strutil function
devWorkerKeyValueConfig = `
listener "tcp" {
purpose = "proxy"
}
worker {
name = "dev-work\u0000er"
description = "A default worker created in dev mode"
initial_upstreams = ["127.0.0.1"]
tags = ["type=dev", "type=local"]
}
`
_, err = Parse(devConfig + devWorkerKeyValueConfig)
assert.Error(t, err)
}
func TestDevCombined(t *testing.T) {
actual, err := DevCombined()
if err != nil {
t.Fatal(err)
}
truePointer := new(bool)
*truePointer = true
exp := &Config{
Eventing: event.DefaultEventerConfig(),
SharedConfig: &configutil.SharedConfig{
DisableMlock: true,
Listeners: []*listenerutil.ListenerConfig{
{
Type: "tcp",
Purpose: []string{"api"},
TLSDisable: true,
CorsEnabled: truePointer,
CorsAllowedOrigins: []string{"*"},
},
{
Type: "tcp",
Purpose: []string{"cluster"},
},
{
Type: "tcp",
Purpose: []string{"ops"},
TLSDisable: true,
},
{
Type: "tcp",
Purpose: []string{"proxy"},
},
},
Seals: []*configutil.KMS{
{
Type: "aead",
Purpose: []string{"root"},
Config: map[string]string{
"aead_type": "aes-gcm",
"key_id": "global_root",
},
},
{
Type: "aead",
Purpose: []string{"worker-auth"},
Config: map[string]string{
"aead_type": "aes-gcm",
"key_id": "global_worker-auth",
},
},
{
Type: "aead",
Purpose: []string{"worker-auth-storage"},
Config: map[string]string{
"aead_type": "aes-gcm",
"key_id": "global_worker-auth-storage",
},
},
{
Type: "aead",
Purpose: []string{"recovery"},
Config: map[string]string{
"aead_type": "aes-gcm",
"key_id": "global_recovery",
},
},
},
},
Controller: &Controller{
Name: "dev-controller",
Description: "A default controller created in dev mode",
},
DevController: true,
Worker: &Worker{
Name: "w_1234567890",
Description: "A default worker created in dev mode",
InitialUpstreams: []string{"127.0.0.1"},
InitialUpstreamsRaw: []interface{}{"127.0.0.1"},
Tags: map[string][]string{
"type": {"dev", "local"},
},
},
}
exp.Listeners[0].RawConfig = actual.Listeners[0].RawConfig
exp.Listeners[1].RawConfig = actual.Listeners[1].RawConfig
exp.Listeners[2].RawConfig = actual.Listeners[2].RawConfig
exp.Listeners[3].RawConfig = actual.Listeners[3].RawConfig
exp.Seals[0].Config["key"] = actual.Seals[0].Config["key"]
exp.Seals[1].Config["key"] = actual.Seals[1].Config["key"]
exp.Seals[2].Config["key"] = actual.Seals[2].Config["key"]
exp.Seals[3].Config["key"] = actual.Seals[3].Config["key"]
exp.DevControllerKey = actual.Seals[0].Config["key"]
exp.DevWorkerAuthKey = actual.Seals[1].Config["key"]
exp.DevWorkerAuthStorageKey = actual.Seals[2].Config["key"]
exp.DevRecoveryKey = actual.Seals[3].Config["key"]
exp.Worker.TagsRaw = actual.Worker.TagsRaw
assert.Equal(t, exp, actual)
}
func TestDevWorkerCredentialStorageDir(t *testing.T) {
t.Parallel()
tests := []struct {
name string
devWorkerProvidedConfiguration string
storagePath string
}{
{
name: "Relative Storage Directory",
devWorkerProvidedConfiguration: `
listener "tcp" {
purpose = "proxy"
}
worker {
name = "w_1234567890"
description = "A default worker created in dev mode"
initial_upstreams = ["127.0.0.1"]
tags {
type = ["dev", "local"]
}
auth_storage_path = ".."
}
`,
storagePath: "..",
},
{
name: "Nonexistent Storage Directory",
devWorkerProvidedConfiguration: `
listener "tcp" {
purpose = "proxy"
}
worker {
name = "w_1234567890"
description = "A default worker created in dev mode"
initial_upstreams = ["127.0.0.1"]
tags {
type = ["dev", "local"]
}
auth_storage_path = "nonexistent_dir/here"
}
`,
storagePath: "nonexistent_dir/here",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
parsed, err := Parse(devConfig + tt.devWorkerProvidedConfiguration)
require.NoError(t, err)
require.Equal(t, tt.storagePath, parsed.Worker.AuthStoragePath)
})
}
}
func TestDevKeyGeneration(t *testing.T) {
t.Parallel()
dk := DevKeyGeneration()
numBytes := 32
require.Equal(t, numBytes, len(dk))
require.NotEqual(t, dk, DevKeyGeneration())
}
func TestParsingName(t *testing.T) {
t.Parallel()
config := `
controller {
name = "%s"
}
worker {
name = "%s"
}
`
controllerEnv := "FOOENV"
workerEnv := "BARENV"
cases := []struct {
name string
templateController string
templateWorker string
envController string
envWorker string
expectedController string
expectedWorker string
}{
{
name: "no env",
templateController: "foobar",
templateWorker: "test_worker_barfoo",
expectedController: "foobar",
expectedWorker: "test_worker_barfoo",
},
{
name: "env",
templateController: fmt.Sprintf("env://%s", controllerEnv),
templateWorker: fmt.Sprintf("env://%s", workerEnv),
envController: "foobar2",
envWorker: "env_name_barfoo2",
expectedController: "foobar2",
expectedWorker: "env_name_barfoo2",
},
}
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
if tt.envController != "" {
os.Setenv(controllerEnv, tt.envController)
}
if tt.envWorker != "" {
os.Setenv(workerEnv, tt.envWorker)
}
out, err := Parse(fmt.Sprintf(config, tt.templateController, tt.templateWorker))
require.NoError(t, err)
assert.Equal(t, tt.expectedController, out.Controller.Name)
assert.Equal(t, tt.expectedWorker, out.Worker.Name)
})
}
}
func TestWorkerTags(t *testing.T) {
defaultStateFn := func(t *testing.T, tags string) {
t.Setenv("BOUNDARY_WORKER_TAGS", tags)
}
tests := []struct {
name string
in string
stateFn func(t *testing.T, tags string)
actualTags string
expWorkerTags map[string][]string
expErr bool
expErrStr string
}{
{
name: "tags in HCL",
in: `
worker {
tags {
type = ["dev", "local"]
typetwo = "devtwo"
}
}`,
expWorkerTags: map[string][]string{
"type": {"dev", "local"},
"typetwo": {"devtwo"},
},
expErr: false,
},
{
name: "tags in HCL key=value",
in: `
worker {
tags = ["type=dev", "type=local", "typetwo=devtwo"]
}
`,
expWorkerTags: map[string][]string{
"type": {"dev", "local"},
"typetwo": {"devtwo"},
},
expErr: false,
},
{
name: "no tags",
in: `
worker {
name = "w_1234567890"
}
`,
expWorkerTags: nil,
expErr: false,
},
{
name: "empty tags",
in: `
worker {
name = "w_1234567890"
tags = {}
}
`,
expWorkerTags: map[string][]string{},
expErr: false,
},
{
name: "empty tags 2",
in: `
worker {
name = "w_1234567890"
tags = []
}
`,
expWorkerTags: map[string][]string{},
expErr: false,
},
{
name: "empty str",
in: `
worker {
tags = ""
}`,
expWorkerTags: map[string][]string{},
expErr: false,
},
{
name: "empty env var",
in: `
worker {
tags = "env://BOUNDARY_WORKER_TAGS"
}`,
expWorkerTags: map[string][]string{},
expErr: false,
},
{
name: "not a url - entire tags block",
in: `
worker {
tags = "\x00"
}`,
expWorkerTags: map[string][]string{},
expErr: true,
expErrStr: `Error parsing worker tags: error parsing url ("parse \"\\x00\": net/url: invalid control character in URL"): not a url`,
},
{
name: "not a url - key's value set to string",
in: `
worker {
tags {
type = "\x00"
}
}
`,
expWorkerTags: map[string][]string{
"type": {"\x00"},
},
expErr: false,
},
{
name: "one tag key",
in: `
worker {
tags = "env://BOUNDARY_WORKER_TAGS"
}`,
stateFn: defaultStateFn,
actualTags: `type = ["dev", "local"]`,
expWorkerTags: map[string][]string{
"type": {"dev", "local"},
},
expErr: false,
},
{
name: "multiple tag keys",
in: `
worker {
tags = "env://BOUNDARY_WORKER_TAGS"
}`,
stateFn: defaultStateFn,
actualTags: `
type = ["dev", "local"]
typetwo = ["devtwo", "localtwo"]
`,
expWorkerTags: map[string][]string{
"type": {"dev", "local"},
"typetwo": {"devtwo", "localtwo"},
},
expErr: false,
},
{
name: "json tags - entire tags block",
in: `
worker {
tags = "env://BOUNDARY_WORKER_TAGS"
}`,
stateFn: defaultStateFn,
actualTags: `
{
"type": ["dev", "local"],
"typetwo": ["devtwo", "localtwo"]
}
`,
expWorkerTags: map[string][]string{
"type": {"dev", "local"},
"typetwo": {"devtwo", "localtwo"},
},
expErr: false,
},
{
name: "json tags - keys specified in the HCL file, values point to env/file",
in: `
worker {
tags = {
type = "env://BOUNDARY_WORKER_TAGS"
typetwo = "env://BOUNDARY_WORKER_TAGS_TWO"
}
}`,
stateFn: func(t *testing.T, tags string) {
defaultStateFn(t, tags)
t.Setenv("BOUNDARY_WORKER_TAGS_TWO", `["devtwo", "localtwo"]`)
},
actualTags: `["dev","local"]`,
expWorkerTags: map[string][]string{
"type": {"dev", "local"},
"typetwo": {"devtwo", "localtwo"},
},
expErr: false,
},
{
name: "json tags - mix n' match",
in: `
worker {
name = "web-prod-us-east-1"
tags {
type = "env://BOUNDARY_WORKER_TYPE_TAGS"
typetwo = "file://type_two_tags.json"
typethree = ["devthree", "localthree"]
}
}
`,
stateFn: func(t *testing.T, tags string) {
workerTypeTags := `["dev", "local"]`
t.Setenv("BOUNDARY_WORKER_TYPE_TAGS", workerTypeTags)
filepath := "./type_two_tags.json"
err := os.WriteFile(filepath, []byte(`["devtwo", "localtwo"]`), 0o666)
require.NoError(t, err)
t.Cleanup(func() {
err := os.Remove(filepath)
require.NoError(t, err)
})
},
expWorkerTags: map[string][]string{
"type": {"dev", "local"},
"typetwo": {"devtwo", "localtwo"},
"typethree": {"devthree", "localthree"},
},
expErr: false,
},
{
name: "bad json tags",
in: `
worker {
tags = {
type = "env://BOUNDARY_WORKER_TAGS"
typetwo = "env://BOUNDARY_WORKER_TAGS"
}
}`,
stateFn: defaultStateFn,
actualTags: `
{
"type": ["dev", "local"],
"typetwo": ["devtwo", "localtwo"]
}
`,
expWorkerTags: nil,
expErr: true,
expErrStr: "Error unmarshalling env var/file contents: json: cannot unmarshal object into Go value of type []string",
},
{
name: "no clean mapping to internal structures",
in: `
worker {
tags = "env://BOUNDARY_WORKER_TAGS"
}`,
stateFn: defaultStateFn,
actualTags: `
worker {
tags {
type = "indeed"
}
}
`,
expErr: true,
expErrStr: "Error decoding the worker's tags: 1 error(s) decoding:\n\n* '[0][worker][0]' expected type 'string', got unconvertible type 'map[string]interface {}', value: 'map[tags:[map[type:indeed]]]'",
},
{
name: "not HCL",
in: `worker {
tags = "env://BOUNDARY_WORKER_TAGS"
}`,
stateFn: defaultStateFn,
actualTags: `not_hcl`,
expErr: true,
expErrStr: "Error decoding raw worker tags: At 1:9: key 'not_hcl' expected start of object ('{') or assignment ('=')",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.stateFn != nil {
tt.stateFn(t, tt.actualTags)
}
c, err := Parse(tt.in)
if tt.expErr {
require.EqualError(t, err, tt.expErrStr)
require.Nil(t, c)
return
}
require.NoError(t, err)
require.NotNil(t, c)
require.NotNil(t, c.Worker)
require.Equal(t, tt.expWorkerTags, c.Worker.Tags)
})
}
}
func TestController_EventingConfig(t *testing.T) {
t.Parallel()
tests := []struct {
name string
config []string
wantEventerConfig *event.EventerConfig
wantErr string
}{
{
name: "default",
wantEventerConfig: event.DefaultEventerConfig(),
},
{
name: "audit-enabled",
config: []string{`
events {
audit_enabled = true
}
`},
wantEventerConfig: &event.EventerConfig{
AuditEnabled: true,
ObservationsEnabled: false,
Sinks: []*event.SinkConfig{
event.DefaultSink(),
},
},
},
{
name: "observations-enabled",
config: []string{`
events {
observations_enabled = true
}
`},
wantEventerConfig: &event.EventerConfig{
AuditEnabled: false,
ObservationsEnabled: true,
Sinks: []*event.SinkConfig{
event.DefaultSink(),
},
},
},
{
name: "no-sink-type-determined",
config: []string{
`events {
audit_enabled = false
observations_enabled = true
sink {
format = "cloudevents-json"
name = "configured-sink"
event_types = [ "audit", "observation" ]
}
}`,
},
wantErr: `error parsing "events": sink type could not be determined`,
},
{
name: "sinks-configured",
config: []string{
`events {
audit_enabled = false
observations_enabled = true
sink {
type = "file"
format = "cloudevents-json"
name = "configured-sink"
event_types = [ "audit", "observation" ]
file {
file_name = "file-name"
rotate_duration = "2m"
}
}
sink {
type = "stderr"
format = "hclog-text"
name = "stderr-sink"
event_types = [ "error" ]
}
}`,
`events {
audit_enabled = false
observations_enabled = true
sink "file" {
format = "cloudevents-json"
name = "configured-sink"
event_types = [ "audit", "observation" ]
file {
file_name = "file-name"
rotate_duration = "2m"
}
}
sink "stderr" {
format = "hclog-text"
name = "stderr-sink"
event_types = [ "error" ]
}
}`,
`events {
audit_enabled = false
observations_enabled = true
sink {
format = "cloudevents-json"
name = "configured-sink"
event_types = [ "audit", "observation" ]
file {
file_name = "file-name"
rotate_duration = "2m"
}
}
sink {
format = "hclog-text"
name = "stderr-sink"
event_types = [ "error" ]
stderr = {}
}
}`,
`{
"events": {
"audit_enabled": false,
"observations_enabled": true,
"sink": [
{
"type": "file",
"format": "cloudevents-json",
"name": "configured-sink",
"event_types": ["audit", "observation"],
"file": {
"file_name": "file-name",
"rotate_duration": "2m"
}
},
{
"format": "hclog-text",
"name": "stderr-sink",
"event_types": ["error"],
"stderr": {}
}
]
}
}`,
},
wantEventerConfig: &event.EventerConfig{
AuditEnabled: false,
ObservationsEnabled: true,
Sinks: []*event.SinkConfig{
{
Type: "file",
Name: "configured-sink",
Format: "cloudevents-json",
EventTypes: []event.Type{"audit", "observation"},
FileConfig: &event.FileSinkTypeConfig{
FileName: "file-name",
RotateDurationHCL: "2m",
RotateDuration: 2 * time.Minute,
},
},
{
Type: "stderr",
Name: "stderr-sink",
Format: "hclog-text",
EventTypes: []event.Type{"error"},
StderrConfig: &event.StderrSinkTypeConfig{},
},
},
},
},
{
name: "audit_config",
config: []string{
`events {
audit_enabled = true
sink {
name = "audit-sink"
format = "cloudevents-json"
event_types = ["audit"]
file {
file_name = "audit.log"
}
audit_config {
audit_filter_overrides {
sensitive = ""
secret = "hmac-sha256"
}
}
}
}`,
},
wantEventerConfig: &event.EventerConfig{
AuditEnabled: true,
Sinks: []*event.SinkConfig{
{
Type: "file",
Name: "audit-sink",
Format: "cloudevents-json",
EventTypes: []event.Type{"audit"},
FileConfig: &event.FileSinkTypeConfig{
FileName: "audit.log",
},
AuditConfig: &event.AuditConfig{
FilterOverridesHCL: map[string]string{
"sensitive": "",
"secret": "hmac-sha256",
},
FilterOverrides: event.AuditFilterOperations{
event.SensitiveClassification: event.NoOperation,
event.SecretClassification: event.HmacSha256Operation,
},
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
for i, conf := range tt.config {
c, err := Parse(conf)
if tt.wantErr != "" {
require.Error(err)
assert.Empty(c)
assert.Equal(tt.wantErr, err.Error(), "config %d want %q and got %q", i, tt.wantErr, err.Error())
return
}
require.NoError(err)
assert.NotEmpty(c)
assert.Equal(tt.wantEventerConfig, c.Eventing)
}
})
}
}
func TestWorkerUpstreams(t *testing.T) {
tests := []struct {
name string
in string
stateFn func(t *testing.T)
expWorkerUpstreams []string
expErr bool
expErrIs error
expErrStr string
}{
{
name: "No Upstreams",
in: `
worker {
name = "test"
}
`,
expWorkerUpstreams: nil,
expErr: false,
},
{
name: "One Upstream",
in: `
worker {
name = "test"
initial_upstreams = ["127.0.0.1"]
}
`,
expWorkerUpstreams: []string{"127.0.0.1"},
expErr: false,
},
{
name: "Multiple Upstreams",
in: `
worker {
name = "test"
initial_upstreams = ["127.0.0.1", "127.0.0.2", "127.0.0.3"]
}
`,
expWorkerUpstreams: []string{"127.0.0.1", "127.0.0.2", "127.0.0.3"},
expErr: false,
},
{
name: "Using env var",
in: `
worker {
name = "test"
initial_upstreams = "env://BOUNDARY_WORKER_UPSTREAMS"
}
`,
stateFn: func(t *testing.T) { t.Setenv("BOUNDARY_WORKER_UPSTREAMS", `["127.0.0.1", "127.0.0.2", "127.0.0.3"]`) },
expWorkerUpstreams: []string{"127.0.0.1", "127.0.0.2", "127.0.0.3"},
expErr: false,
},
{
name: "Using env var - invalid input 1",
in: `
worker {
name = "test"
initial_upstreams = "env://BOUNDARY_WORKER_UPSTREAMS"
}
`,
stateFn: func(t *testing.T) {
upstreams := `
worker {
initial_upstreams = ["127.0.0.1"]
}
`
t.Setenv("BOUNDARY_WORKER_UPSTREAMS", upstreams)
},
expWorkerUpstreams: nil,
expErr: true,
expErrStr: "Failed to parse worker upstreams: failed to unmarshal env/file contents: invalid character 'w' looking for beginning of value",
},
{
name: "Using env var - invalid input 2",
in: `
worker {
name = "test"
initial_upstreams = "env://BOUNDARY_WORKER_UPSTREAMS"
}
`,
stateFn: func(t *testing.T) { t.Setenv("BOUNDARY_WORKER_UPSTREAMS", `initial_upstreams = ["127.0.0.1"]`) },
expWorkerUpstreams: nil,
expErr: true,
expErrStr: "Failed to parse worker upstreams: failed to unmarshal env/file contents: invalid character 'i' looking for beginning of value",
},
{
name: "Unsupported object",
in: `
worker {
name = "test"
initial_upstreams = {
ip = "127.0.0.1"
ip = "127.0.0.2"
}
}
`,
expWorkerUpstreams: nil,
expErr: true,
expErrStr: "Failed to parse worker upstreams: unexpected type \"[]map[string]interface {}\"",
},
{
name: "Worker initial_upstreams set to invalid url",
in: `
worker {
name = "test"
initial_upstreams = "env://\x00"
}`,
expWorkerUpstreams: nil,
expErr: true,
expErrIs: parseutil.ErrNotAUrl,
},
{
name: "Worker using deprecated controllers field",
in: `
worker {
name = "test"
controllers = ["127.0.0.1", "127.0.0.2", "127.0.0.3"]
}`,
expWorkerUpstreams: []string{"127.0.0.1", "127.0.0.2", "127.0.0.3"},
expErr: false,
},
{
name: "Different values in controllers and initial_upstreams field",
in: `
worker {
name = "test"
controllers = ["127.0.0.1", "127.0.0.2", "127.0.0.3"]
initial_upstreams = ["127.0.0.1"]
}`,
expWorkerUpstreams: nil,
expErr: true,
expErrStr: "Failed to parse worker upstreams: both initial_upstreams and controllers fields are populated",
},
{
name: "Identical values in controllers and initial_upstreams field",
in: `
worker {
name = "test"
controllers = ["127.0.0.1"]
initial_upstreams = ["127.0.0.1"]
}`,
expWorkerUpstreams: nil,
expErr: true,
expErrStr: "Failed to parse worker upstreams: both initial_upstreams and controllers fields are populated",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.stateFn != nil {
tt.stateFn(t)
}
c, err := Parse(tt.in)
if tt.expErr {
if tt.expErrIs != nil {
require.ErrorIs(t, err, tt.expErrIs)
} else {
require.EqualError(t, err, tt.expErrStr)
}
require.Nil(t, c)
return
}
require.NoError(t, err)
require.NotNil(t, c)
require.NotNil(t, c.Worker)
require.EqualValues(t, tt.expWorkerUpstreams, c.Worker.InitialUpstreams)
})
}
}
func TestControllerDescription(t *testing.T) {
tests := []struct {
name string
in string
envDescription string
expDescription string
expErr bool
expErrStr string
}{
{
name: "Valid controller description from env var",
in: `
controller {
description = "env://CONTROLLER_DESCRIPTION"
}`,
envDescription: "Test controller description",
expDescription: "Test controller description",
expErr: false,
}, {
name: "Invalid controller description from env var",
in: `
controller {
description = "\uTest controller description"
}`,
expErr: true,
expErrStr: "At 3:22: illegal char escape",
}, {
name: "Not a URL, non-printable description",
in: `
controller {
description = "\x00"
}`,
expErr: true,
expErrStr: "Controller description contains non-printable characters",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Setenv("CONTROLLER_DESCRIPTION", tt.envDescription)
c, err := Parse(tt.in)
if tt.expErr {
require.EqualError(t, err, tt.expErrStr)
require.Nil(t, c)
return
}
require.NoError(t, err)
require.NotNil(t, c)
require.NotNil(t, c.Controller)
require.Equal(t, tt.expDescription, c.Controller.Description)
})
}
}
func TestWorkerDescription(t *testing.T) {
tests := []struct {
name string
in string
envDescription string
expDescription string
expErr bool
expErrStr string
}{
{
name: "Valid worker description from env var",
in: `
worker {
description = "env://WORKER_DESCRIPTION"
}`,
envDescription: "Test worker description",
expDescription: "Test worker description",
expErr: false,
}, {
name: "Invalid worker description",
in: `
worker {
description = "\uTest worker description"
}`,
expErr: true,
expErrStr: "At 3:22: illegal char escape",
}, {
name: "Not a URL, non-printable description",
in: `
worker {
description = "\x00"
}`,
expErr: true,
expErrStr: "Worker description contains non-printable characters",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Setenv("WORKER_DESCRIPTION", tt.envDescription)
c, err := Parse(tt.in)
if tt.expErr {
require.EqualError(t, err, tt.expErrStr)
require.Nil(t, c)
return
}
require.NoError(t, err)
require.NotNil(t, c)
require.NotNil(t, c.Worker)
require.Equal(t, tt.expDescription, c.Worker.Description)
})
}
}
func TestPluginExecutionDir(t *testing.T) {
tests := []struct {
name string
in string
envPluginExecutionDir string
expPluginExecutionDir string
expErr bool
expErrStr string
}{
{
name: "Valid plugin execution dir from env var",
in: `
plugins {
execution_dir = "env://PLUGIN_EXEC_DIR"
}`,
envPluginExecutionDir: `/var/run/boundary/plugin-exec`,
expPluginExecutionDir: `/var/run/boundary/plugin-exec`,
expErr: false,
}, {
name: "Invalid plugin execution dir from env var",
in: `
plugins {
execution_dir ="\ubad plugin directory"
}`,
expErr: true,
expErrStr: "At 3:28: illegal char escape",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Setenv("PLUGIN_EXEC_DIR", tt.envPluginExecutionDir)
p, err := Parse(tt.in)
if tt.expErr {
require.EqualError(t, err, tt.expErrStr)
require.Nil(t, p)
return
}
require.NoError(t, err)
require.NotNil(t, p)
require.NotNil(t, p.Plugins)
require.Equal(t, tt.expPluginExecutionDir, p.Plugins.ExecutionDir)
})
}
}
func TestDatabaseMaxConnections(t *testing.T) {
tests := []struct {
name string
in string
envMaxOpenConnections string
expMaxOpenConnections int
expErr bool
expErrStr string
}{
{
name: "Valid integer value",
in: `
controller {
name = "example-controller"
database {
max_open_connections = 5
}
}`,
expMaxOpenConnections: 5,
expErr: false,
},
{
name: "Valid string value",
in: `
controller {
name = "example-controller"
database {
max_open_connections = "5"
}
}`,
expMaxOpenConnections: 5,
expErr: false,
},
{
name: "Invalid value string",
in: `
controller {
name = "example-controller"
database {
max_open_connections = "string bad"
}
}`,
expErr: true,
expErrStr: "Database max open connections value is not an int: " +
"strconv.Atoi: parsing \"string bad\": invalid syntax",
},
{
name: "Invalid value type",
in: `
controller {
name = "example-controller"
database {
max_open_connections = false
}
}`,
expErr: true,
expErrStr: "Database max open connections: unsupported type \"bool\"",
},
{
name: "Valid env var",
in: `
controller {
name = "example-controller"
database {
max_open_connections = "env://ENV_MAX_CONN"
}
}`,
expMaxOpenConnections: 8,
envMaxOpenConnections: "8",
expErr: false,
},
{
name: "Invalid env var",
in: `
controller {
name = "example-controller"
database {
max_open_connections = "env://ENV_MAX_CONN"
}
}`,
envMaxOpenConnections: "bogus value",
expErr: true,
expErrStr: "Database max open connections value is not an int: " +
"strconv.Atoi: parsing \"bogus value\": invalid syntax",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Setenv("ENV_MAX_CONN", tt.envMaxOpenConnections)
c, err := Parse(tt.in)
if tt.expErr {
require.EqualError(t, err, tt.expErrStr)
require.Nil(t, c)
return
}
require.NoError(t, err)
require.NotNil(t, c)
require.NotNil(t, c.Controller)
require.NotNil(t, c.Controller.Database)
require.Equal(t, tt.expMaxOpenConnections, c.Controller.Database.MaxOpenConnections)
})
}
}
func TestDatabaseMaxIdleConnections(t *testing.T) {
tests := []struct {
name string
in string
envMaxIdleConnections string
expMaxIdleConnections int
expErr bool
expErrStr string
}{
{
name: "Valid integer value",
in: `
controller {
name = "example-controller"
database {
max_idle_connections = 5
}
}`,
expMaxIdleConnections: 5,
expErr: false,
},
{
name: "Valid integer string",
in: `
controller {
name = "example-controller"
database {
max_idle_connections = "5"
}
}`,
expMaxIdleConnections: 5,
expErr: false,
},
{
name: "Invalid value string",
in: `
controller {
name = "example-controller"
database {
max_idle_connections = "string bad"
}
}`,
expErr: true,
expErrStr: "Database max idle connections value is not a uint: " +
"strconv.Atoi: parsing \"string bad\": invalid syntax",
},
{
name: "Invalid value type",
in: `
controller {
name = "example-controller"
database {
max_idle_connections = false
}
}`,
expErr: true,
expErrStr: "Database max idle connections: unsupported type \"bool\"",
},
{
name: "Valid env var",
in: `
controller {
name = "example-controller"
database {
max_idle_connections = "env://ENV_MAX_IDLE_CONN"
}
}`,
expMaxIdleConnections: 8,
envMaxIdleConnections: "8",
expErr: false,
},
{
name: "Invalid env var",
in: `
controller {
name = "example-controller"
database {
max_idle_connections = "env://ENV_MAX_IDLE_CONN"
}
}`,
envMaxIdleConnections: "bogus value",
expErr: true,
expErrStr: "Database max idle connections value is not a uint: " +
"strconv.Atoi: parsing \"bogus value\": invalid syntax",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Setenv("ENV_MAX_IDLE_CONN", tt.envMaxIdleConnections)
c, err := Parse(tt.in)
if tt.expErr {
require.EqualError(t, err, tt.expErrStr)
require.Nil(t, c)
return
}
require.NoError(t, err)
require.NotNil(t, c)
require.NotNil(t, c.Controller)
require.NotNil(t, c.Controller.Database)
require.Equal(t, tt.expMaxIdleConnections, *c.Controller.Database.MaxIdleConnections)
})
}
}
func TestDatabaseConnMaxIdleTimeDuration(t *testing.T) {
tests := []struct {
name string
in string
envConnMaxIdleTimeDuration string
expConnMaxIdleTimeDuration time.Duration
expErr bool
expErrStr string
}{
{
name: "Valid duration value",
in: `
controller {
name = "example-controller"
database {
max_idle_time = "5m"
}
}`,
expConnMaxIdleTimeDuration: time.Minute * 5,
expErr: false,
},
{
name: "Valid env var value",
envConnMaxIdleTimeDuration: "5m",
in: `
controller {
name = "example-controller"
database {
max_idle_time = "env://ENV_CONN_MAX_IDLE_TIME"
}
}`,
expConnMaxIdleTimeDuration: time.Minute * 5,
expErr: false,
},
{
name: "Invalid value string",
in: `
controller {
name = "example-controller"
database {
max_idle_time = "string bad"
}
}`,
expErr: true,
expErrStr: "Connection max idle time is not a duration: " +
"strconv.ParseInt: parsing \"string ba\": invalid syntax",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Setenv("ENV_CONN_MAX_IDLE_TIME", tt.envConnMaxIdleTimeDuration)
c, err := Parse(tt.in)
if tt.expErr {
require.EqualError(t, err, tt.expErrStr)
require.Nil(t, c)
return
}
require.NoError(t, err)
require.NotNil(t, c)
require.NotNil(t, c.Controller)
require.NotNil(t, c.Controller.Database)
require.Equal(t, tt.expConnMaxIdleTimeDuration, *c.Controller.Database.ConnMaxIdleTimeDuration)
})
}
}