Skip to content

Commit

Permalink
Add annotation to use template path on disk (hashicorp#222)
Browse files Browse the repository at this point in the history
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
hashicorp#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-file-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 hashicorp#84
  • Loading branch information
jfroche authored Apr 5, 2021
1 parent 525c03a commit 50cfa58
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 5 deletions.
3 changes: 3 additions & 0 deletions agent-inject/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,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

Expand Down
15 changes: 15 additions & 0 deletions agent-inject/agent/annotations.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -414,6 +423,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)
Expand Down
131 changes: 131 additions & 0 deletions agent-inject/agent/annotations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,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
Expand Down
12 changes: 9 additions & 3 deletions agent-inject/agent/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -102,8 +103,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)
Expand All @@ -112,6 +117,7 @@ func (a *Agent) newTemplateConfigs() []*Template {
}

tmpl := &Template{
Source: templateFile,
Contents: template,
Destination: filePathAndName,
LeftDelim: "{{",
Expand Down
15 changes: 13 additions & 2 deletions agent-inject/agent/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,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",
Expand Down Expand Up @@ -112,8 +116,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 {
Expand All @@ -140,6 +144,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")
}
Expand Down

0 comments on commit 50cfa58

Please sign in to comment.