diff --git a/CHANGELOG.md b/CHANGELOG.md index 848ba243..5492e30b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ Changes: * `k8s.io/utils` v0.0.0-20240502163921-fe8a2dddb1d0 => v0.0.0-20240711033017-18e509b52bc8 * `sigs.k8s.io/controller-runtime` v0.18.4 => v0.19.1 +Features: +* Add annotations for customizing template delimiters [GH-664](https://github.com/hashicorp/vault-k8s/pull/664) + Bugs: * Disable handling update on pods [GH-619](https://github.com/hashicorp/vault-k8s/pull/619) diff --git a/agent-inject/agent/agent.go b/agent-inject/agent/agent.go index 4a21bbb5..e7224e58 100644 --- a/agent-inject/agent/agent.go +++ b/agent-inject/agent/agent.go @@ -234,6 +234,12 @@ type Secret struct { // ErrMissingKey is used to control how the template behaves when attempting // to index a struct or a map key that does not exist ErrMissingKey bool + + // LeftDelimiter is the optional left delimiter to use when rendering a templated secret + LeftDelimiter string + + // RightDelimiter is the optional right delimiter to use when rendering a templated secret + RightDelimiter string } type Vault struct { diff --git a/agent-inject/agent/annotations.go b/agent-inject/agent/annotations.go index 4a12a8a1..1f2e46da 100644 --- a/agent-inject/agent/annotations.go +++ b/agent-inject/agent/annotations.go @@ -80,6 +80,22 @@ const ( // If not provided, the template content key annotation is used. AnnotationAgentInjectTemplateFile = "vault.hashicorp.com/agent-inject-template-file" + // AnnotationAgentInjectTemplateLeftDelim is the key annotation that configures Vault + // Agent what left delimiter to use for rendering the secrets. The name + // of the template is any unique string after "vault.hashicorp.com/agent-template-left-delim-", + // such as "vault.hashicorp.com/agent-template-left-delim-foobar". This should map + // to the same unique value provided in "vault.hashicorp.com/agent-inject-secret-". + // If not provided, a default left delimiter is used as defined by https://www.vaultproject.io/docs/agent/template#left_delimiter + AnnotationAgentInjectTemplateLeftDelim = "vault.hashicorp.com/agent-template-left-delim" + + // AnnotationAgentInjectTemplateRightDelim is the key annotation that configures Vault + // Agent what right delimiter to use for rendering the secrets. The name + // of the template is any unique string after "vault.hashicorp.com/agent-template-right-delim-", + // such as "vault.hashicorp.com/agent-template-right-delim-foobar". This should map + // to the same unique value provided in "vault.hashicorp.com/agent-inject-secret-". + // If not provided, a default right delimiter is used as defined by https://www.vaultproject.io/docs/agent/template#right_delimiter + AnnotationAgentInjectTemplateRightDelim = "vault.hashicorp.com/agent-template-right-delim" + // AnnotationAgentInjectToken is the annotation key for injecting the // auto-auth token into the secrets volume (e.g. /vault/secrets/token) AnnotationAgentInjectToken = "vault.hashicorp.com/agent-inject-token" @@ -646,6 +662,8 @@ func (a *Agent) secrets() ([]*Secret, error) { } secret.MountPath = a.annotationsSecretValue(AnnotationVaultSecretVolumePath, secret.RawName, a.Annotations[AnnotationVaultSecretVolumePath]) secret.Command = a.annotationsSecretValue(AnnotationAgentInjectCommand, secret.RawName, "") + secret.LeftDelimiter = a.annotationsSecretValue(AnnotationAgentInjectTemplateLeftDelim, secret.RawName, "") + secret.RightDelimiter = a.annotationsSecretValue(AnnotationAgentInjectTemplateRightDelim, secret.RawName, "") secret.FilePathAndName = a.annotationsSecretValue(AnnotationAgentInjectFile, secret.RawName, "") secret.FilePermission = a.annotationsSecretValue(AnnotationAgentInjectFilePermission, secret.RawName, "") diff --git a/agent-inject/agent/annotations_test.go b/agent-inject/agent/annotations_test.go index 1574897e..757e0964 100644 --- a/agent-inject/agent/annotations_test.go +++ b/agent-inject/agent/annotations_test.go @@ -461,39 +461,107 @@ func TestSecretMixedTemplatesAnnotations(t *testing.T) { "vault.hashicorp.com/agent-inject-template-only-template": "onlyTemplate", "vault.hashicorp.com/agent-inject-template-file-only-template-file": "onlyTemplateFile", + + "vault.hashicorp.com/agent-inject-secret-barfoo": "test1", + "vault.hashicorp.com/agent-inject-template-barfoo": "", + "vault.hashicorp.com/agent-template-left-delim-barfoo": "${", + "vault.hashicorp.com/agent-template-right-delim-barfoo": "}", + "vault.hashicorp.com/agent-inject-template-file-barfoo": "/etc/config.tmpl", + + "vault.hashicorp.com/agent-inject-secret-test3": "test3", + "vault.hashicorp.com/agent-inject-template-test3": "foobarTemplate3", + "vault.hashicorp.com/agent-inject-template-file-test3": "", + "vault.hashicorp.com/agent-template-left-delim-test3": "", + "vault.hashicorp.com/agent-template-right-delim-test3": "", + + "vault.hashicorp.com/agent-inject-template-only-template-2": "onlyTemplate2", + "vault.hashicorp.com/agent-template-left-delim-only-template-2": "${", + "vault.hashicorp.com/agent-template-right-delim-only-template-2": "}", + + "vault.hashicorp.com/agent-inject-template-file-only-template-file-2": "onlyTemplateFile2", + "vault.hashicorp.com/agent-template-left-delim-only-template-file-2": "${", + "vault.hashicorp.com/agent-template-right-delim-only-template-file-2": "}", }, map[string]Secret{ "foobar": { - Name: "foobar", - RawName: "foobar", - Path: "test1", - Template: "", - TemplateFile: "/etc/config.tmpl", - MountPath: secretVolumePath, + Name: "foobar", + RawName: "foobar", + Path: "test1", + Template: "", + LeftDelimiter: "", + RightDelimiter: "", + TemplateFile: "/etc/config.tmpl", + MountPath: secretVolumePath, }, "test2": { - Name: "test2", - RawName: "test2", - Path: "test2", - Template: "foobarTemplate", - TemplateFile: "", - MountPath: secretVolumePath, + Name: "test2", + RawName: "test2", + Path: "test2", + Template: "foobarTemplate", + LeftDelimiter: "", + RightDelimiter: "", + TemplateFile: "", + MountPath: secretVolumePath, }, "only-template": { - Name: "only-template", - RawName: "only-template", - Path: "", - Template: "onlyTemplate", - TemplateFile: "", - MountPath: secretVolumePath, + Name: "only-template", + RawName: "only-template", + Path: "", + Template: "onlyTemplate", + LeftDelimiter: "", + RightDelimiter: "", + TemplateFile: "", + MountPath: secretVolumePath, }, "only-template-file": { - Name: "only-template-file", - RawName: "only-template-file", - Path: "", - Template: "", - TemplateFile: "onlyTemplateFile", - MountPath: secretVolumePath, + Name: "only-template-file", + RawName: "only-template-file", + Path: "", + Template: "", + LeftDelimiter: "", + RightDelimiter: "", + TemplateFile: "onlyTemplateFile", + MountPath: secretVolumePath, + }, + "barfoo": { + Name: "barfoo", + RawName: "barfoo", + Path: "test1", + Template: "", + LeftDelimiter: "${", + RightDelimiter: "}", + TemplateFile: "/etc/config.tmpl", + MountPath: secretVolumePath, + }, + "test3": { + Name: "test3", + RawName: "test3", + Path: "test3", + Template: "foobarTemplate3", + LeftDelimiter: "", + RightDelimiter: "", + TemplateFile: "", + MountPath: secretVolumePath, + }, + "only-template-2": { + Name: "only-template-2", + RawName: "only-template-2", + Path: "", + Template: "onlyTemplate2", + LeftDelimiter: "${", + RightDelimiter: "}", + TemplateFile: "", + MountPath: secretVolumePath, + }, + "only-template-file-2": { + Name: "only-template-file-2", + RawName: "only-template-file-2", + Path: "", + Template: "", + LeftDelimiter: "${", + RightDelimiter: "}", + TemplateFile: "onlyTemplateFile2", + MountPath: secretVolumePath, }, }, }, diff --git a/agent-inject/agent/config.go b/agent-inject/agent/config.go index e4437291..d2bbcd9b 100644 --- a/agent-inject/agent/config.go +++ b/agent-inject/agent/config.go @@ -14,6 +14,8 @@ import ( const ( DefaultMapTemplate = "{{ with secret \"%s\" }}{{ range $k, $v := .Data }}{{ $k }}: {{ $v }}\n{{ end }}{{ end }}" DefaultJSONTemplate = "{{ with secret \"%s\" }}{{ .Data | toJSON }}\n{{ end }}" + DefaultLeftDelim = "{{" + DefaultRightDelim = "}}" DefaultTemplateType = "map" PidFile = "/home/vault/.pid" TokenFile = "/home/vault/.vault-token" @@ -195,6 +197,16 @@ func (a *Agent) newTemplateConfigs() []*Template { } } + leftDelim := secret.LeftDelimiter + if leftDelim == "" { + leftDelim = DefaultLeftDelim + } + + rightDelim := secret.RightDelimiter + if rightDelim == "" { + rightDelim = DefaultRightDelim + } + filePathAndName := fmt.Sprintf("%s/%s", secret.MountPath, secret.Name) if secret.FilePathAndName != "" { filePathAndName = filepath.Join(secret.MountPath, secret.FilePathAndName) @@ -204,8 +216,8 @@ func (a *Agent) newTemplateConfigs() []*Template { Source: templateFile, Contents: template, Destination: filePathAndName, - LeftDelim: "{{", - RightDelim: "}}", + LeftDelim: leftDelim, + RightDelim: rightDelim, Command: secret.Command, ErrMissingKey: secret.ErrMissingKey, } diff --git a/agent-inject/agent/config_test.go b/agent-inject/agent/config_test.go index b1dd94bb..bdacb3f0 100644 --- a/agent-inject/agent/config_test.go +++ b/agent-inject/agent/config_test.go @@ -51,6 +51,11 @@ func TestNewConfig(t *testing.T) { "vault.hashicorp.com/agent-inject-command-bar": "pkill -HUP app", + "vault.hashicorp.com/agent-inject-secret-baz": "db/creds/baz", + "vault.hashicorp.com/agent-inject-template-baz": `[[ with secret "db/creds/baz" ]][[ range $k, $v := .Data ]][[ $k ]]: [[ $v ]]\n[[ end ]][[ end ]]`, + "vault.hashicorp.com/agent-template-left-delim-baz": "[[", + "vault.hashicorp.com/agent-template-right-delim-baz": "]]", + AnnotationAgentCacheEnable: "true", } @@ -121,8 +126,8 @@ func TestNewConfig(t *testing.T) { t.Error("agent Cache should be disabled for init containers") } - if len(config.Templates) != 6 { - t.Errorf("expected 4 template, got %d", len(config.Templates)) + if len(config.Templates) != 7 { + t.Errorf("expected 7 templates, got %d", len(config.Templates)) } for _, template := range config.Templates { @@ -134,6 +139,10 @@ func TestNewConfig(t *testing.T) { if template.Contents != "template foo" { t.Errorf("expected template contents to be %s, got %s", "template foo", template.Contents) } + + if template.LeftDelim != DefaultLeftDelim || template.RightDelim != DefaultRightDelim { + t.Errorf("expected default delimiters to be %s (left) and %s (right), got %s (left) and %s (right)", DefaultLeftDelim, DefaultRightDelim, template.LeftDelim, template.RightDelim) + } } else if strings.Contains(template.Destination, "bar") { if template.Destination != "/vault/secrets/bar" { t.Errorf("expected template destination to be %s, got %s", "/vault/secrets/bar", template.Destination) @@ -170,6 +179,10 @@ func TestNewConfig(t *testing.T) { if template.Destination != "/vault/secrets/just-template-file" { t.Errorf("expected template destination to be %s, got %s", "/vault/secrets/just-template-file", template.Destination) } + } else if strings.Contains(template.Destination, "baz") { + if template.LeftDelim != "[[" || template.RightDelim != "]]" { + t.Errorf("expected default delimiters to be %s (left) and %s (right), got %s (left) and %s (right)", "[[", "]]", template.LeftDelim, template.RightDelim) + } } else { t.Error("shouldn't have got here") } @@ -627,7 +640,7 @@ func TestConfigVaultAgentTemplateConfig(t *testing.T) { AnnotationTemplateConfigExitOnRetryFailure: "true", }, &TemplateConfig{ - ExitOnRetryFailure: true, + ExitOnRetryFailure: true, MaxConnectionsPerHost: 0, }, }, @@ -637,7 +650,7 @@ func TestConfigVaultAgentTemplateConfig(t *testing.T) { AnnotationTemplateConfigExitOnRetryFailure: "false", }, &TemplateConfig{ - ExitOnRetryFailure: false, + ExitOnRetryFailure: false, MaxConnectionsPerHost: 0, }, }, @@ -647,9 +660,9 @@ func TestConfigVaultAgentTemplateConfig(t *testing.T) { AnnotationTemplateConfigStaticSecretRenderInterval: "10s", }, &TemplateConfig{ - ExitOnRetryFailure: true, + ExitOnRetryFailure: true, StaticSecretRenderInterval: "10s", - MaxConnectionsPerHost: 0, + MaxConnectionsPerHost: 0, }, }, { @@ -658,7 +671,7 @@ func TestConfigVaultAgentTemplateConfig(t *testing.T) { AnnotationTemplateConfigMaxConnectionsPerHost: "100", }, &TemplateConfig{ - ExitOnRetryFailure: true, + ExitOnRetryFailure: true, MaxConnectionsPerHost: 100, }, }, @@ -666,7 +679,7 @@ func TestConfigVaultAgentTemplateConfig(t *testing.T) { "template_config_empty", map[string]string{}, &TemplateConfig{ - ExitOnRetryFailure: true, + ExitOnRetryFailure: true, MaxConnectionsPerHost: 0, }, },