275 lines
8.5 KiB
Go
275 lines
8.5 KiB
Go
|
// 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())
|
||
|
}
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
}
|