diff --git a/api/tasks.go b/api/tasks.go index 6e4afb2c9..cbc6fef1a 100644 --- a/api/tasks.go +++ b/api/tasks.go @@ -1047,16 +1047,21 @@ func (v *Vault) Canonicalize() { } type Secret struct { - Name string `hcl:"name,label"` - Provider string `hcl:"provider,optional"` - Path string `hcl:"path,optional"` - Config map[string]any `hcl:"config,block"` + Name string `hcl:"name,label"` + Provider string `hcl:"provider,optional"` + Path string `hcl:"path,optional"` + Config map[string]any `hcl:"config,block"` + Env map[string]string `hcl:"env,block"` } func (s *Secret) Canonicalize() { if len(s.Config) == 0 { s.Config = nil } + + if len(s.Env) == 0 { + s.Env = nil + } } // NewTask creates and initializes a new Task. diff --git a/api/tasks_test.go b/api/tasks_test.go index f3769b441..2cc5c6c14 100644 --- a/api/tasks_test.go +++ b/api/tasks_test.go @@ -514,6 +514,7 @@ func TestTask_Canonicalize_Secret(t *testing.T) { Provider: "test-provider", Path: "/test/path", Config: make(map[string]any), + Env: make(map[string]string), } expected := &Secret{ @@ -521,6 +522,7 @@ func TestTask_Canonicalize_Secret(t *testing.T) { Provider: "test-provider", Path: "/test/path", Config: nil, + Env: nil, } testSecret.Canonicalize() diff --git a/client/allocrunner/taskrunner/secrets_hook.go b/client/allocrunner/taskrunner/secrets_hook.go index 616ff0172..73de9e457 100644 --- a/client/allocrunner/taskrunner/secrets_hook.go +++ b/client/allocrunner/taskrunner/secrets_hook.go @@ -198,7 +198,7 @@ func (h *secretsHook) buildSecretProviders(secretDir string) ([]TemplateProvider tmplProvider = append(tmplProvider, p) } default: - plug, err := commonplugins.NewExternalSecretsPlugin(h.clientConfig.CommonPluginDir, s.Provider) + plug, err := commonplugins.NewExternalSecretsPlugin(h.clientConfig.CommonPluginDir, s.Provider, s.Env) if err != nil { multierror.Append(mErr, err) continue diff --git a/client/commonplugins/secrets_plugin.go b/client/commonplugins/secrets_plugin.go index 530bf1348..110f23d6f 100644 --- a/client/commonplugins/secrets_plugin.go +++ b/client/commonplugins/secrets_plugin.go @@ -41,9 +41,16 @@ type externalSecretsPlugin struct { // pluginPath is the path on the host to the plugin executable pluginPath string + + // env is optional envVars passed to the plugin process + env map[string]string } -func NewExternalSecretsPlugin(commonPluginDir string, name string) (*externalSecretsPlugin, error) { +// NewExternalSecretsPlugin creates an instance of a secrets plugin by validating the plugin +// binary exists and is executable, and parsing any string key/value pairs out of the config +// which will be used as environment variables for Fetch. +func NewExternalSecretsPlugin(commonPluginDir string, name string, env map[string]string) (*externalSecretsPlugin, error) { + // validate plugin executable := filepath.Join(commonPluginDir, SecretsPluginDir, name) f, err := os.Stat(executable) if err != nil { @@ -58,6 +65,7 @@ func NewExternalSecretsPlugin(commonPluginDir string, name string) (*externalSec return &externalSecretsPlugin{ pluginPath: executable, + env: env, }, nil } @@ -96,6 +104,10 @@ func (e *externalSecretsPlugin) Fetch(ctx context.Context, path string) (*Secret "CPI_OPERATION=fetch", } + for env, val := range e.env { + cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", env, val)) + } + stdout, stderr, err := runPlugin(cmd, SecretsKillTimeout) if err != nil { return nil, err diff --git a/client/commonplugins/secrets_plugin_test.go b/client/commonplugins/secrets_plugin_test.go index 6c92e123f..ab3f6af3b 100644 --- a/client/commonplugins/secrets_plugin_test.go +++ b/client/commonplugins/secrets_plugin_test.go @@ -21,7 +21,7 @@ func TestExternalSecretsPlugin_Fingerprint(t *testing.T) { t.Run("runs successfully", func(t *testing.T) { pluginDir, pluginName := setupTestPlugin(t, fmt.Appendf([]byte{}, "#!/bin/sh\ncat < 0 { + _ = multierror.Append(&mErr, fmt.Errorf("%s provider cannot use the env block", s.Provider)) + } + } else { + if len(s.Config) > 0 { + _ = multierror.Append(&mErr, fmt.Errorf("custom plugin provider %s cannot use the config block", s.Provider)) + } + } + return mErr.ErrorOrNil() } diff --git a/nomad/structs/structs_test.go b/nomad/structs/structs_test.go index 8ccf1b779..c92f20dc5 100644 --- a/nomad/structs/structs_test.go +++ b/nomad/structs/structs_test.go @@ -4,6 +4,7 @@ package structs import ( + "errors" "fmt" "net" "os" @@ -6556,16 +6557,7 @@ func TestSecrets_Validate(t *testing.T) { Path: "test-path", Provider: "test-provider", }, - expectErr: fmt.Errorf("secret name cannot be empty"), - }, - { - name: "invalid name", - secret: &Secret{ - Name: "bad-name@", - Path: "test-path", - Provider: "test-provider", - }, - expectErr: fmt.Errorf("secret name must match regex %s", validSecretName), + expectErr: errors.New("secret name cannot be empty"), }, { name: "missing provider", @@ -6573,7 +6565,7 @@ func TestSecrets_Validate(t *testing.T) { Name: "testsecret", Path: "test-path", }, - expectErr: fmt.Errorf("secret provider cannot be empty"), + expectErr: errors.New("secret provider cannot be empty"), }, { name: "missing path", @@ -6581,7 +6573,43 @@ func TestSecrets_Validate(t *testing.T) { Name: "testsecret", Provider: "test-provier", }, - expectErr: fmt.Errorf("secret path cannot be empty"), + expectErr: errors.New("secret path cannot be empty"), + }, + { + name: "nomad provider fails with env", + secret: &Secret{ + Name: "test-secret", + Provider: "nomad", + Path: "test", + Env: map[string]string{ + "test": "test", + }, + }, + expectErr: errors.New("nomad provider cannot use the env block"), + }, + { + name: "vault provider fails with env", + secret: &Secret{ + Name: "test-secret", + Provider: "vault", + Path: "test", + Env: map[string]string{ + "test": "test", + }, + }, + expectErr: errors.New("vault provider cannot use the env block"), + }, + { + name: "custom provider fails with config", + secret: &Secret{ + Name: "test-secret", + Provider: "test", + Path: "test", + Config: map[string]any{ + "test": "test", + }, + }, + expectErr: errors.New("custom plugin provider test cannot use the config block"), }, }