diff --git a/agent-inject/agent/agent.go b/agent-inject/agent/agent.go index 75e94cb4..b0fd8c74 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 file is the optional path on disk to the custom template to use when rendering the secret. + TemplateFile 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..26c7762a 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" + // AnnotationAgentInjectTemplateFile 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-file-", + // such as "vault.hashicorp.com/agent-inject-template-file-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. + AnnotationAgentInjectTemplateFile = "vault.hashicorp.com/agent-inject-template-file" + // 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 == "" { + templateFileAnnotation := fmt.Sprintf("%s-%s", AnnotationAgentInjectTemplateFile, raw) + if val, ok := a.Annotations[templateFileAnnotation]; ok { + s.TemplateFile = 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..917e7701 100644 --- a/agent-inject/agent/annotations_test.go +++ b/agent-inject/agent/annotations_test.go @@ -473,6 +473,137 @@ func TestTemplateShortcuts(t *testing.T) { } } +func TestSecretMixedTemplatesAnnotations(t *testing.T) { + tests := []struct { + annotations map[string]string + expectedSecrets map[string]Secret + }{ + { + map[string]string{ + "vault.hashicorp.com/agent-inject-secret-foobar": "test1", + "vault.hashicorp.com/agent-inject-template-foobar": "", + "vault.hashicorp.com/agent-inject-template-file-foobar": "/etc/config.tmpl", + "vault.hashicorp.com/agent-inject-secret-test2": "test2", + "vault.hashicorp.com/agent-inject-template-test2": "foobarTemplate", + "vault.hashicorp.com/agent-inject-template-file-test2": "", + }, + map[string]Secret{ + "foobar": Secret{ + Name: "foobar", + Path: "test1", + Template: "", + TemplateFile: "/etc/config.tmpl", + MountPath: secretVolumePath, + }, + "test2": Secret{ + Name: "test2", + Path: "test2", + Template: "foobarTemplate", + TemplateFile: "", + MountPath: secretVolumePath, + }, + }, + }, + } + for _, tt := range tests { + pod := testPod(tt.annotations) + 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) + } + + var patches []*jsonpatch.JsonPatchOperation + + agent, err := New(pod, patches) + if err != nil { + t.Errorf("got error, shouldn't have: %s", err) + } + + if len(agent.Secrets) != len(tt.expectedSecrets) { + t.Errorf("agent Secrets length was %d, expected %d", len(agent.Secrets), len(tt.expectedSecrets)) + } + + for _, s := range agent.Secrets { + if s == nil { + t.Error("Got a nil agent Secret") + t.FailNow() + } + expectedSecret, found := tt.expectedSecrets[s.Name] + if !found { + t.Errorf("Unexpected agent secret name %q", s.Name) + t.FailNow() + } + if !reflect.DeepEqual(expectedSecret, *s) { + t.Errorf("expected secret %+v, got agent secret %+v", expectedSecret, *s) + } + } + } +} + +func TestSecretTemplateFileAnnotations(t *testing.T) { + tests := []struct { + annotations map[string]string + expectedKey string + expectedTemplate string + expectedTemplateFile 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-file-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-file-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].TemplateFile != tt.expectedTemplateFile { + t.Errorf("expected template file path %s, got %s", tt.expectedTemplateFile, agent.Secrets[0].TemplateFile) + } + + } +} + 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..dc54e343 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) + templateFile := secret.TemplateFile + if templateFile == "" { + 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: templateFile, Contents: template, Destination: filePathAndName, LeftDelim: "{{", diff --git a/agent-inject/agent/config_test.go b/agent-inject/agent/config_test.go index 3a9a332f..4f51514b 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-file-template": "with-file-template", + fmt.Sprintf("%s-%s", AnnotationAgentInjectTemplateFile, "with-file-template"): "/etc/file-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-file-template") { + if template.Source != "/etc/file-template" { + t.Errorf("expected template file path to be %s, got %s", "/etc/file-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") }