From edc11f22a07c799f5d6629731cca348226f6a573 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Roche?= Date: Mon, 15 Feb 2021 21:06:36 +0100 Subject: [PATCH] Add annotation to use template path on disk We currently have two options to define vault agent templates: either define the template configuration as an inline template in the annotation or configure the vault agent directly. The former is really not handy when template is getting complex, the latter forces us to manage the whole vault agent configuration. We add a new annotation that enables the vault agent to inject secrets from a template file on the container disk. Since https://github.com/hashicorp/vault-k8s/pull/212, this template can be present in volume defined on the container. Annotation example: ```yaml vault.hashicorp.com/agent-inject-secret-foo: 'database/roles/app' vault.hashicorp.com/agent-inject-template-source-foo: '/etc/my-app/config.toml.tmpl' vault.hashicorp.com/agent-inject-file-foo: '/etc/my-app/config.toml', vault.hashicorp.com/agent-copy-volume-mounts: 'MyContainerNameWithVolumes' ``` If a template content is also defined in annotation (using `vault.hashicorp.com/agent-inject-template`, the template on disk won't be used. refs #84 --- agent-inject/agent/agent.go | 3 ++ agent-inject/agent/annotations.go | 15 +++++++ agent-inject/agent/annotations_test.go | 60 ++++++++++++++++++++++++++ agent-inject/agent/config.go | 12 ++++-- agent-inject/agent/config_test.go | 15 ++++++- 5 files changed, 100 insertions(+), 5 deletions(-) diff --git a/agent-inject/agent/agent.go b/agent-inject/agent/agent.go index 75e94cb4..dceba3f5 100644 --- a/agent-inject/agent/agent.go +++ b/agent-inject/agent/agent.go @@ -146,6 +146,9 @@ type Secret struct { // Template is the optional custom template to use when rendering the secret. Template string + // Template source is the optional path on disk to the custom template to use when rendering the secret. + TemplateSource string + // Mount Path for the volume holding the rendered secret file MountPath string diff --git a/agent-inject/agent/annotations.go b/agent-inject/agent/annotations.go index fc153c71..088a063d 100644 --- a/agent-inject/agent/annotations.go +++ b/agent-inject/agent/annotations.go @@ -45,6 +45,15 @@ const ( // If not provided, a default generic template is used. AnnotationAgentInjectTemplate = "vault.hashicorp.com/agent-inject-template" + // AnnotationAgentInjectTemplateSource is the optional key annotation that configures Vault + // Agent what template on disk to use for rendering the secrets. The name + // of the template is any unique string after "vault.hashicorp.com/agent-inject-template-source-", + // such as "vault.hashicorp.com/agent-inject-template-source-foobar". This should map + // to the same unique value provided in "vault.hashicorp.com/agent-inject-secret-". + // The value is the filename and path of the template used by the agent to render the secrets. + // If not provided, the template content key annotation is used. + AnnotationAgentInjectTemplateSource = "vault.hashicorp.com/agent-inject-template-source" + // AnnotationAgentInjectToken is the annotation key for injecting the token // from auth/token/lookup-self AnnotationAgentInjectToken = "vault.hashicorp.com/agent-inject-token" @@ -379,6 +388,12 @@ func (a *Agent) secrets() []*Secret { if val, ok := a.Annotations[templateName]; ok { s.Template = val } + if s.Template == "" { + templateSourceAnnotation := fmt.Sprintf("%s-%s", AnnotationAgentInjectTemplateSource, raw) + if val, ok := a.Annotations[templateSourceAnnotation]; ok { + s.TemplateSource = val + } + } s.MountPath = a.Annotations[AnnotationVaultSecretVolumePath] mountPathAnnotationName := fmt.Sprintf("%s-%s", AnnotationVaultSecretVolumePath, raw) diff --git a/agent-inject/agent/annotations_test.go b/agent-inject/agent/annotations_test.go index 94857533..79f2820e 100644 --- a/agent-inject/agent/annotations_test.go +++ b/agent-inject/agent/annotations_test.go @@ -473,6 +473,66 @@ func TestTemplateShortcuts(t *testing.T) { } } +func TestSecretTemplateSourceAnnotations(t *testing.T) { + tests := []struct { + annotations map[string]string + expectedKey string + expectedTemplate string + expectedTemplateSource string + }{ + { + map[string]string{ + "vault.hashicorp.com/agent-inject-secret-foobar": "test1", + "vault.hashicorp.com/agent-inject-template-foobar": "foobarTemplate", + "vault.hashicorp.com/agent-inject-template-source-foobar": "/etc/config.tmpl", + }, "foobar", "foobarTemplate", "", + }, + { + map[string]string{ + "vault.hashicorp.com/agent-inject-secret-foobar": "test1", + "vault.hashicorp.com/agent-inject-template-foobar": "", + "vault.hashicorp.com/agent-inject-template-source-foobar": "/etc/config.tmpl", + }, "foobar", "", "/etc/config.tmpl", + }, + } + + for _, tt := range tests { + pod := testPod(tt.annotations) + var patches []*jsonpatch.JsonPatchOperation + + agentConfig := AgentConfig{ + "", "http://foobar:8200", "test", "test", true, "100", "1000", + DefaultAgentRunAsSameUser, DefaultAgentSetSecurityContext, + } + err := Init(pod, agentConfig) + if err != nil { + t.Errorf("got error, shouldn't have: %s", err) + } + + agent, err := New(pod, patches) + if err != nil { + t.Errorf("got error, shouldn't have: %s", err) + } + + if len(agent.Secrets) == 0 { + t.Error("Secrets length was zero, it shouldn't have been") + } + + if agent.Secrets[0].Name != tt.expectedKey { + t.Errorf("expected name %s, got %s", tt.expectedKey, agent.Secrets[0].Name) + } + + if agent.Secrets[0].Template != tt.expectedTemplate { + t.Errorf("expected template %s, got %s", tt.expectedTemplate, agent.Secrets[0].Template) + } + + if agent.Secrets[0].TemplateSource != tt.expectedTemplateSource { + t.Errorf("expected template source path %s, got %s", tt.expectedTemplateSource, agent.Secrets[0].TemplateSource) + } + + } +} + func TestSecretCommandAnnotations(t *testing.T) { tests := []struct { annotations map[string]string diff --git a/agent-inject/agent/config.go b/agent-inject/agent/config.go index c503954e..ce5e2bc7 100644 --- a/agent-inject/agent/config.go +++ b/agent-inject/agent/config.go @@ -70,10 +70,11 @@ type Sink struct { type Template struct { CreateDestDirs bool `json:"create_dest_dirs,omitempty"` Destination string `json:"destination"` - Contents string `json:"contents"` + Contents string `json:"contents,omitempty"` LeftDelim string `json:"left_delimiter,omitempty"` RightDelim string `json:"right_delimiter,omitempty"` Command string `json:"command,omitempty"` + Source string `json:"source,omitempty"` } // Listener defines the configuration for Vault Agent Cache Listener @@ -92,8 +93,12 @@ func (a *Agent) newTemplateConfigs() []*Template { var templates []*Template for _, secret := range a.Secrets { template := secret.Template - if template == "" { - template = fmt.Sprintf(DefaultTemplate, secret.Path) + templateSource := secret.TemplateSource + if templateSource == "" { + template = secret.Template + if template == "" { + template = fmt.Sprintf(DefaultTemplate, secret.Path) + } } filePathAndName := fmt.Sprintf("%s/%s", secret.MountPath, secret.Name) @@ -102,6 +107,7 @@ func (a *Agent) newTemplateConfigs() []*Template { } tmpl := &Template{ + Source: templateSource, Contents: template, Destination: filePathAndName, LeftDelim: "{{", diff --git a/agent-inject/agent/config_test.go b/agent-inject/agent/config_test.go index 3a9a332f..d95fe41c 100644 --- a/agent-inject/agent/config_test.go +++ b/agent-inject/agent/config_test.go @@ -32,6 +32,10 @@ func TestNewConfig(t *testing.T) { "vault.hashicorp.com/agent-inject-secret-different-path": "different-path", fmt.Sprintf("%s-%s", AnnotationVaultSecretVolumePath, "different-path"): "/etc/container_environment", + // render this secret from a template on disk + "vault.hashicorp.com/agent-inject-secret-with-source-template": "with-source-template", + fmt.Sprintf("%s-%s", AnnotationAgentInjectTemplateSource, "with-source-template"): "/etc/source-template", + "vault.hashicorp.com/agent-inject-command-bar": "pkill -HUP app", AnnotationAgentCacheEnable: "true", @@ -108,8 +112,8 @@ func TestNewConfig(t *testing.T) { t.Error("agent Cache should be disabled for init containers") } - if len(config.Templates) != 3 { - t.Errorf("expected 3 template, got %d", len(config.Templates)) + if len(config.Templates) != 4 { + t.Errorf("expected 4 template, got %d", len(config.Templates)) } for _, template := range config.Templates { @@ -136,6 +140,13 @@ func TestNewConfig(t *testing.T) { if template.Destination != "/etc/container_environment/different-path" { t.Errorf("expected template destination to be %s, got %s", "/etc/container_environment", template.Destination) } + } else if strings.Contains(template.Destination, "with-source-template") { + if template.Source != "/etc/source-template" { + t.Errorf("expected template source path to be %s, got %s", "/etc/source-template", template.Source) + } + if template.Contents != "" { + t.Errorf("expected template contents to be empty, got %s", template.Contents) + } } else { t.Error("shouldn't have got here") }