secrets: pass key/value config data to plugins as env (#26455)

Co-authored-by: Michael Schurter <mschurter@hashicorp.com>
Co-authored-by: Tim Gross <tgross@hashicorp.com>
This commit is contained in:
Michael Smithhisler
2025-08-18 11:11:21 -04:00
parent e9e1631b8c
commit 10ed46cbd4
16 changed files with 231 additions and 54 deletions

View File

@@ -0,0 +1,51 @@
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: BUSL-1.1
variable "secret_value" {
type = string
description = "The value of the randomly generated secret for this test"
}
job "custom_secret" {
constraint {
attribute = "${attr.kernel.name}"
value = "linux"
}
update {
min_healthy_time = "1s"
}
group "group" {
task "task" {
driver = "docker"
config {
image = "busybox:1"
command = "/bin/sh"
args = ["-c", "sleep 300"]
}
secret "testsecret" {
provider = "test_secret_plugin"
path = "some/path"
env {
// The custom plugin will output this as part of the result field
TEST_ENV = "${var.secret_value}"
}
}
env {
TEST_SECRET = "${secret.testsecret.TEST_ENV}"
}
resources {
cpu = 128
memory = 64
}
}
}
}

View File

@@ -1,6 +1,11 @@
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: BUSL-1.1
variable "secret_path" {
type = string
description = "The path of the vault secret"
}
job "nomad_secret" {
constraint {
@@ -8,6 +13,10 @@ job "nomad_secret" {
value = "linux"
}
update {
min_healthy_time = "1s"
}
group "group" {
task "task" {
@@ -22,7 +31,7 @@ job "nomad_secret" {
secret "testsecret" {
provider = "nomad"
path = "SECRET_PATH"
path = "${var.secret_path}"
config {
namespace = "default"
}

View File

@@ -1,6 +1,11 @@
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: BUSL-1.1
variable "secret_path" {
type = string
description = "The path of the vault secret"
}
job "vault_secret" {
constraint {
@@ -8,6 +13,10 @@ job "vault_secret" {
value = "linux"
}
update {
min_healthy_time = "1s"
}
group "group" {
task "task" {
@@ -24,7 +33,7 @@ job "vault_secret" {
secret "testsecret" {
provider = "vault"
path = "SECRET_PATH"
path = "${var.secret_path}"
config {
engine = "kv_v2"
}

View File

@@ -38,26 +38,16 @@ func TestVaultSecret(t *testing.T) {
submission, cleanJob := jobs3.Submit(t,
"./input/vault_secret.hcl",
jobs3.DisableRandomJobID(),
jobs3.DisableRandomJobID(), // our path won't match the secret path with a random jobID
jobs3.Namespace(ns),
jobs3.Detach(),
jobs3.ReplaceInJobSpec("SECRET_PATH", secretFullPath),
jobs3.Var("secret_path", secretFullPath),
)
t.Cleanup(cleanJob)
// Ensure the placed allocation reaches the running state. If the test fails
// here, it's likely due to permissions or pathing of the secret errors.
must.NoError(
t,
e2e.WaitForAllocStatusExpected(submission.JobID(), ns, []string{"running"}),
must.Sprint("expected running allocation"),
)
// Validate the nomad variable was read and parsed into the expected
// environment variable
out, err := e2e.Command("nomad", "exec", submission.AllocID("group"), "env")
must.NoError(t, err)
must.StrContains(t, out, fmt.Sprintf("TEST_SECRET=%s", secretValue))
out := submission.Exec("group", "task", []string{"env"})
must.StrContains(t, out.Stdout, fmt.Sprintf("TEST_SECRET=%s", secretValue))
}
func TestNomadSecret(t *testing.T) {
@@ -97,22 +87,31 @@ func TestNomadSecret(t *testing.T) {
"./input/nomad_secret.hcl",
jobs3.DisableRandomJobID(),
jobs3.Namespace(ns),
jobs3.Detach(),
jobs3.ReplaceInJobSpec("SECRET_PATH", secretFullPath),
jobs3.Var("secret_path", secretFullPath),
)
t.Cleanup(cleanJob)
// Ensure the placed allocation reaches the running state. If the test fails
// here, it's likely due to permissions or pathing of the secret errors.
must.NoError(
t,
e2e.WaitForAllocStatusExpected(submission.JobID(), ns, []string{"running"}),
must.Sprint("expected running allocation"),
// Validate the nomad variable was read and parsed into the expected
// environment variable
out := submission.Exec("group", "task", []string{"env"})
must.StrContains(t, out.Stdout, fmt.Sprintf("TEST_SECRET=%s", secretValue))
}
func TestPluginSecret(t *testing.T) {
// Generate a uuid value for the secret plugins env block which it will output
// as a part of the result field.
secretValue := uuid.Generate()
submission, cleanJob := jobs3.Submit(t,
"./input/custom_secret.hcl",
jobs3.DisableRandomJobID(),
jobs3.Namespace(ns),
jobs3.Var("secret_value", secretValue),
)
t.Cleanup(cleanJob)
// Validate the nomad variable was read and parsed into the expected
// environment variable
out, err := e2e.Command("nomad", "exec", submission.AllocID("group"), "env")
must.NoError(t, err)
must.StrContains(t, out, fmt.Sprintf("TEST_SECRET=%s", secretValue))
out := submission.Exec("group", "task", []string{"env"})
must.StrContains(t, out.Stdout, fmt.Sprintf("TEST_SECRET=%s", secretValue))
}

View File

@@ -0,0 +1,25 @@
#!/bin/bash
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: BUSL-1.1
fingerprint() {
echo {\"type\": \"secrets\", \"version\": \"0.0.1\"}
}
fetch() {
# return any passed environment variables as output
echo '{"result":{'$(printenv | awk -F= '{printf "\"%s\":\"%s\",", $1, $2}' | sed 's/,$//')'}}'
}
case "$1" in
fingerprint)
fingerprint
;;
fetch)
fetch
;;
*)
exit 1
esac

View File

@@ -113,6 +113,10 @@ echo "Installing additional CNI network configs"
# copy of nomad's "bridge" for connect+cni test (e2e/connect/)
sudo mv /tmp/linux/cni/nomad_bridge_copy.conflist /opt/cni/config/
echo "Installing CPI test plugins"
mkdir_for_root /opt/nomad/data/common_plugins/secrets
sudo mv /tmp/linux/common-plugins/test_secret_plugin.sh /opt/nomad/data/common_plugins/secrets/test_secret_plugin
# Podman
echo "Installing Podman"
sudo apt-get -y install podman catatonit