Skip to content

Commit dccdfb9

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.
1 parent cfb2bbc commit dccdfb9

File tree

7 files changed

+843
-0
lines changed

7 files changed

+843
-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: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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/nomad/structs"
13+
)
14+
15+
// NomadSecretItems is a map type returned by the secret template function
16+
// that can be iterated over in templates. It wraps a map[string]string.
17+
type NomadSecretItems map[string]string
18+
19+
// nomadSecretConfig contains the configuration needed to create
20+
// secret plugin template functions.
21+
type nomadSecretConfig struct {
22+
// CommonPluginDir is the directory containing common plugins
23+
CommonPluginDir string
24+
25+
// Secrets is the list of secrets configured for the task
26+
Secrets []*structs.Secret
27+
}
28+
29+
// nomadSecretFuncs returns template functions for accessing secret plugins.
30+
// The returned FuncMap can be merged into consul-template's ExtFuncMap.
31+
func nomadSecretFuncs(cfg *nomadSecretConfig) template.FuncMap {
32+
return template.FuncMap{
33+
"nomadSecret": nomadSecretFunc(cfg),
34+
}
35+
}
36+
37+
// nomadSecretFunc returns a template function that fetches secrets from a
38+
// pre-configured secret block by name. The returned map can be iterated
39+
// over in templates using range.
40+
//
41+
// Usage in templates:
42+
//
43+
// {{ range $k, $v := nomadSecret "app_secrets" }}
44+
// {{ $k }}={{ $v }}
45+
// {{ end }}
46+
//
47+
// Or with the `with` clause:
48+
//
49+
// {{ with nomadSecret "db_creds" }}
50+
// DB_USER={{ index . "username" }}
51+
// DB_PASS={{ index . "password" }}
52+
// {{ end }}
53+
func nomadSecretFunc(cfg *nomadSecretConfig) func(secretName string) (NomadSecretItems, error) {
54+
// Build a lookup map of secrets by name for efficient access
55+
secretsByName := make(map[string]*structs.Secret, len(cfg.Secrets))
56+
for _, s := range cfg.Secrets {
57+
if s != nil {
58+
secretsByName[s.Name] = s
59+
}
60+
}
61+
62+
return func(secretName string) (NomadSecretItems, error) {
63+
if secretName == "" {
64+
return nil, fmt.Errorf("secret name is required")
65+
}
66+
67+
// Look up the secret configuration by name
68+
secret, ok := secretsByName[secretName]
69+
if !ok {
70+
return nil, fmt.Errorf("secret %q not found in task configuration", secretName)
71+
}
72+
73+
// Create the secrets plugin
74+
plugin, err := commonplugins.NewExternalSecretsPlugin(cfg.CommonPluginDir, secret.Provider, secret.Env)
75+
if err != nil {
76+
return nil, fmt.Errorf("failed to create secrets plugin %q for secret %q: %w", secret.Provider, secretName, err)
77+
}
78+
79+
// Fetch the secrets using the configured path
80+
ctx := context.Background()
81+
resp, err := plugin.Fetch(ctx, secret.Path)
82+
if err != nil {
83+
return nil, fmt.Errorf("failed to fetch secret %q from plugin %q at path %q: %w", secretName, secret.Provider, secret.Path, err)
84+
}
85+
86+
if resp.Error != nil {
87+
return nil, fmt.Errorf("secret plugin %q returned error for secret %q: %s", secret.Provider, secretName, *resp.Error)
88+
}
89+
90+
return NomadSecretItems(resp.Result), nil
91+
}
92+
}

0 commit comments

Comments
 (0)