Skip to content

Commit 65d2a61

Browse files
committed
template: add nomadSecret template function
Add a secret template function that allows templates to fetch secrets from pre-configured secret blocks. This enables iteration over secrets returned by external secret plugins. The function takes a secret block name as its single argument and looks up the provider, path, and environment variables from the task's secret configuration. It automatically passes NOMAD_NAMESPACE and NOMAD_JOB_ID to the plugin along with any env vars defined in the secret block.
1 parent cfb2bbc commit 65d2a61

File tree

7 files changed

+902
-0
lines changed

7 files changed

+902
-0
lines changed

.changelog/27214.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:improvement
2+
template: Add `nomadSecret` template function for iterating over secrets from external plugins
3+
```

client/allocrunner/taskrunner/secrets_hook.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ func (h *secretsHook) Prestart(ctx context.Context, req *interfaces.TaskPrestart
128128
MaxTemplateEventRate: template.DefaultMaxTemplateEventRate,
129129
NomadNamespace: h.nomadNamespace,
130130
NomadToken: req.NomadToken,
131+
JobID: h.jobId,
131132
TaskID: req.Alloc.ID + "-" + req.Task.Name,
132133
Logger: h.logger,
133134

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: BUSL-1.1
3+
4+
package template
5+
6+
import (
7+
"context"
8+
"fmt"
9+
"text/template"
10+
11+
"github.com/hashicorp/nomad/client/commonplugins"
12+
"github.com/hashicorp/nomad/client/taskenv"
13+
"github.com/hashicorp/nomad/nomad/structs"
14+
)
15+
16+
// NomadSecretItems is a map type returned by the secret template function
17+
// that can be iterated over in templates. It wraps a map[string]string.
18+
type NomadSecretItems map[string]string
19+
20+
// nomadSecretConfig contains the configuration needed to create
21+
// secret plugin template functions.
22+
type nomadSecretConfig struct {
23+
// CommonPluginDir is the directory containing common plugins
24+
CommonPluginDir string
25+
26+
// Namespace is the Nomad namespace for the task
27+
Namespace string
28+
29+
// JobID is the job ID for the task
30+
JobID string
31+
32+
// Secrets is the list of secrets configured for the task
33+
Secrets []*structs.Secret
34+
}
35+
36+
// nomadSecretFuncs returns template functions for accessing secret plugins.
37+
// The returned FuncMap can be merged into consul-template's ExtFuncMap.
38+
func nomadSecretFuncs(cfg *nomadSecretConfig) template.FuncMap {
39+
return template.FuncMap{
40+
"nomadSecret": nomadSecretFunc(cfg),
41+
}
42+
}
43+
44+
// nomadSecretFunc returns a template function that fetches secrets from a
45+
// pre-configured secret block by name. The returned map can be iterated
46+
// over in templates using range.
47+
//
48+
// Usage in templates:
49+
//
50+
// {{ range $k, $v := nomadSecret "app_secrets" }}
51+
// {{ $k }}={{ $v }}
52+
// {{ end }}
53+
//
54+
// Or with the `with` clause:
55+
//
56+
// {{ with nomadSecret "db_creds" }}
57+
// DB_USER={{ index . "username" }}
58+
// DB_PASS={{ index . "password" }}
59+
// {{ end }}
60+
func nomadSecretFunc(cfg *nomadSecretConfig) func(secretName string) (NomadSecretItems, error) {
61+
// Build a lookup map of secrets by name for efficient access
62+
secretsByName := make(map[string]*structs.Secret, len(cfg.Secrets))
63+
for _, s := range cfg.Secrets {
64+
if s != nil {
65+
secretsByName[s.Name] = s
66+
}
67+
}
68+
69+
return func(secretName string) (NomadSecretItems, error) {
70+
if secretName == "" {
71+
return nil, fmt.Errorf("secret name is required")
72+
}
73+
74+
// Look up the secret configuration by name
75+
secret, ok := secretsByName[secretName]
76+
if !ok {
77+
return nil, fmt.Errorf("secret %q not found in task configuration", secretName)
78+
}
79+
80+
// Build environment variables for the plugin
81+
env := make(map[string]string)
82+
// Copy any env vars configured on the secret block
83+
for k, v := range secret.Env {
84+
env[k] = v
85+
}
86+
// Add/override with Nomad-specific env vars
87+
env[taskenv.Namespace] = cfg.Namespace
88+
env[taskenv.JobID] = cfg.JobID
89+
90+
// Create the secrets plugin
91+
plugin, err := commonplugins.NewExternalSecretsPlugin(cfg.CommonPluginDir, secret.Provider, env)
92+
if err != nil {
93+
return nil, fmt.Errorf("failed to create secrets plugin %q for secret %q: %w", secret.Provider, secretName, err)
94+
}
95+
96+
// Fetch the secrets using the configured path
97+
ctx := context.Background()
98+
resp, err := plugin.Fetch(ctx, secret.Path)
99+
if err != nil {
100+
return nil, fmt.Errorf("failed to fetch secret %q from plugin %q at path %q: %w", secretName, secret.Provider, secret.Path, err)
101+
}
102+
103+
if resp.Error != nil {
104+
return nil, fmt.Errorf("secret plugin %q returned error for secret %q: %s", secret.Provider, secretName, *resp.Error)
105+
}
106+
107+
return NomadSecretItems(resp.Result), nil
108+
}
109+
}

0 commit comments

Comments
 (0)