open-vault/command/agent_generate_config_test.go

275 lines
8.5 KiB
Go
Raw Normal View History

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package command
import (
"bytes"
"context"
"reflect"
"regexp"
"testing"
"time"
)
// TestConstructTemplates tests the construcTemplates helper function
func TestConstructTemplates(t *testing.T) {
ctx, cancelContextFunc := context.WithTimeout(context.Background(), 5*time.Second)
defer cancelContextFunc()
client, closer := testVaultServerWithSecrets(ctx, t)
defer closer()
cases := map[string]struct {
paths []string
expected []generatedConfigEnvTemplate
expectedError bool
}{
"kv-v1-simple": {
paths: []string{"kv-v1/foo"},
expected: []generatedConfigEnvTemplate{
{Contents: `{{ with secret "kv-v1/foo" }}{{ .Data.password }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_PASSWORD"},
{Contents: `{{ with secret "kv-v1/foo" }}{{ .Data.user }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_USER"},
},
expectedError: false,
},
"kv-v2-simple": {
paths: []string{"kv-v2/foo"},
expected: []generatedConfigEnvTemplate{
{Contents: `{{ with secret "kv-v2/data/foo" }}{{ .Data.data.password }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_PASSWORD"},
{Contents: `{{ with secret "kv-v2/data/foo" }}{{ .Data.data.user }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_USER"},
},
expectedError: false,
},
"kv-v2-data-in-path": {
paths: []string{"kv-v2/data/foo"},
expected: []generatedConfigEnvTemplate{
{Contents: `{{ with secret "kv-v2/data/foo" }}{{ .Data.data.password }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_PASSWORD"},
{Contents: `{{ with secret "kv-v2/data/foo" }}{{ .Data.data.user }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_USER"},
},
expectedError: false,
},
"kv-v1-nested": {
paths: []string{"kv-v1/app-1/*"},
expected: []generatedConfigEnvTemplate{
{Contents: `{{ with secret "kv-v1/app-1/bar" }}{{ .Data.password }}{{ end }}`, ErrorOnMissingKey: true, Name: "BAR_PASSWORD"},
{Contents: `{{ with secret "kv-v1/app-1/bar" }}{{ .Data.user }}{{ end }}`, ErrorOnMissingKey: true, Name: "BAR_USER"},
{Contents: `{{ with secret "kv-v1/app-1/foo" }}{{ .Data.password }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_PASSWORD"},
{Contents: `{{ with secret "kv-v1/app-1/foo" }}{{ .Data.user }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_USER"},
{Contents: `{{ with secret "kv-v1/app-1/nested/baz" }}{{ .Data.password }}{{ end }}`, ErrorOnMissingKey: true, Name: "BAZ_PASSWORD"},
{Contents: `{{ with secret "kv-v1/app-1/nested/baz" }}{{ .Data.user }}{{ end }}`, ErrorOnMissingKey: true, Name: "BAZ_USER"},
},
expectedError: false,
},
"kv-v2-nested": {
paths: []string{"kv-v2/app-1/*"},
expected: []generatedConfigEnvTemplate{
{Contents: `{{ with secret "kv-v2/data/app-1/bar" }}{{ .Data.data.password }}{{ end }}`, ErrorOnMissingKey: true, Name: "BAR_PASSWORD"},
{Contents: `{{ with secret "kv-v2/data/app-1/bar" }}{{ .Data.data.user }}{{ end }}`, ErrorOnMissingKey: true, Name: "BAR_USER"},
{Contents: `{{ with secret "kv-v2/data/app-1/foo" }}{{ .Data.data.password }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_PASSWORD"},
{Contents: `{{ with secret "kv-v2/data/app-1/foo" }}{{ .Data.data.user }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_USER"},
{Contents: `{{ with secret "kv-v2/data/app-1/nested/baz" }}{{ .Data.data.password }}{{ end }}`, ErrorOnMissingKey: true, Name: "BAZ_PASSWORD"},
{Contents: `{{ with secret "kv-v2/data/app-1/nested/baz" }}{{ .Data.data.user }}{{ end }}`, ErrorOnMissingKey: true, Name: "BAZ_USER"},
},
expectedError: false,
},
"kv-v1-multi-path": {
paths: []string{"kv-v1/foo", "kv-v1/app-1/bar"},
expected: []generatedConfigEnvTemplate{
{Contents: `{{ with secret "kv-v1/foo" }}{{ .Data.password }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_PASSWORD"},
{Contents: `{{ with secret "kv-v1/foo" }}{{ .Data.user }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_USER"},
{Contents: `{{ with secret "kv-v1/app-1/bar" }}{{ .Data.password }}{{ end }}`, ErrorOnMissingKey: true, Name: "BAR_PASSWORD"},
{Contents: `{{ with secret "kv-v1/app-1/bar" }}{{ .Data.user }}{{ end }}`, ErrorOnMissingKey: true, Name: "BAR_USER"},
},
expectedError: false,
},
"kv-v2-multi-path": {
paths: []string{"kv-v2/foo", "kv-v2/app-1/bar"},
expected: []generatedConfigEnvTemplate{
{Contents: `{{ with secret "kv-v2/data/foo" }}{{ .Data.data.password }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_PASSWORD"},
{Contents: `{{ with secret "kv-v2/data/foo" }}{{ .Data.data.user }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_USER"},
{Contents: `{{ with secret "kv-v2/data/app-1/bar" }}{{ .Data.data.password }}{{ end }}`, ErrorOnMissingKey: true, Name: "BAR_PASSWORD"},
{Contents: `{{ with secret "kv-v2/data/app-1/bar" }}{{ .Data.data.user }}{{ end }}`, ErrorOnMissingKey: true, Name: "BAR_USER"},
},
expectedError: false,
},
"kv-v1-path-not-found": {
paths: []string{"kv-v1/does/not/exist"},
expected: nil,
expectedError: true,
},
"kv-v2-path-not-found": {
paths: []string{"kv-v2/does/not/exist"},
expected: nil,
expectedError: true,
},
"kv-v1-early-wildcard": {
paths: []string{"kv-v1/*/foo"},
expected: nil,
expectedError: true,
},
"kv-v2-early-wildcard": {
paths: []string{"kv-v2/*/foo"},
expected: nil,
expectedError: true,
},
}
for name, tc := range cases {
name, tc := name, tc
t.Run(name, func(t *testing.T) {
templates, err := constructTemplates(ctx, client, tc.paths)
if tc.expectedError {
if err == nil {
t.Fatal("an error was expected but the test succeeded")
}
} else {
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(tc.expected, templates) {
t.Fatalf("unexpected output; want: %v, got: %v", tc.expected, templates)
}
}
})
}
}
// TestGenerateConfiguration tests the generateConfiguration helper function
func TestGenerateConfiguration(t *testing.T) {
ctx, cancelContextFunc := context.WithTimeout(context.Background(), 5*time.Second)
defer cancelContextFunc()
client, closer := testVaultServerWithSecrets(ctx, t)
defer closer()
cases := map[string]struct {
flagExec string
flagPaths []string
expected *regexp.Regexp
expectedError bool
}{
"kv-v1-simple": {
flagExec: "./my-app arg1 arg2",
flagPaths: []string{"kv-v1/foo"},
expected: regexp.MustCompile(`
auto_auth \{
method \{
type = "token_file"
config \{
token_file_path = ".*/.vault-token"
}
}
}
template_config \{
static_secret_render_interval = "5m"
exit_on_retry_failure = true
}
vault \{
address = "https://127.0.0.1:[0-9]{5}"
}
env_template "FOO_PASSWORD" \{
contents = "\{\{ with secret \\"kv-v1/foo\\" }}\{\{ .Data.password }}\{\{ end }}"
error_on_missing_key = true
}
env_template "FOO_USER" \{
contents = "\{\{ with secret \\"kv-v1/foo\\" }}\{\{ .Data.user }}\{\{ end }}"
error_on_missing_key = true
}
exec \{
command = \["./my-app", "arg1", "arg2"\]
restart_on_secret_changes = "always"
restart_stop_signal = "SIGTERM"
}
`),
expectedError: false,
},
"kv-v2-default-exec": {
flagExec: "",
flagPaths: []string{"kv-v2/foo"},
expected: regexp.MustCompile(`
auto_auth \{
method \{
type = "token_file"
config \{
token_file_path = ".*/.vault-token"
}
}
}
template_config \{
static_secret_render_interval = "5m"
exit_on_retry_failure = true
}
vault \{
address = "https://127.0.0.1:[0-9]{5}"
}
env_template "FOO_PASSWORD" \{
contents = "\{\{ with secret \\"kv-v2/data/foo\\" }}\{\{ .Data.data.password }}\{\{ end }}"
error_on_missing_key = true
}
env_template "FOO_USER" \{
contents = "\{\{ with secret \\"kv-v2/data/foo\\" }}\{\{ .Data.data.user }}\{\{ end }}"
error_on_missing_key = true
}
exec \{
command = \["env"\]
restart_on_secret_changes = "always"
restart_stop_signal = "SIGTERM"
}
`),
expectedError: false,
},
}
for name, tc := range cases {
name, tc := name, tc
t.Run(name, func(t *testing.T) {
var config bytes.Buffer
c, err := generateConfiguration(ctx, client, tc.flagExec, tc.flagPaths)
c.WriteTo(&config)
if tc.expectedError {
if err == nil {
t.Fatal("an error was expected but the test succeeded")
}
} else {
if err != nil {
t.Fatal(err)
}
if !tc.expected.MatchString(config.String()) {
t.Fatalf("unexpected output; want: %v, got: %v", tc.expected.String(), config.String())
}
}
})
}
}