Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

F1707 secret ref interpolation #31

Merged
merged 3 commits into from
Sep 27, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 28 additions & 3 deletions internal/provider/data_env_variables.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ func (*dataEnvVariables) Schema(ctx context.Context) *tfprotov5.Schema {
Computed: true,
Sensitive: true,
},
{
Name: "secret_refs",
Type: tftypes.Map{ElementType: tftypes.String},
Description: "Map of environment variables that refer to an existing secret key for their values.",
DescriptionKind: tfprotov5.StringKindMarkdown,
Computed: true,
},
}

return &tfprotov5.Schema{
Expand Down Expand Up @@ -104,8 +111,22 @@ func (d *dataEnvVariables) Read(ctx context.Context, config map[string]tftypes.V
envVariables := copyMap(inputEnvVariables)
secrets := copyMap(inputSecrets)

regexPattern := "{{\\s?%s\\s?}}"
// if any env variables contain secret refs, pull them out
secretRefs := make(map[string]tftypes.Value, 0)
refRegexPattern := "{{\\s?secret\\((.+)\\)\\s?}}"
regex := regexp.MustCompile(refRegexPattern)
for k, v := range envVariables {
result := regex.FindStringSubmatch(extractStringFromTfValue(v))
// if we found a proper match, the result will contain a slice
// with the full match as the first item and the capture group as the second
if len(result) > 1 {
ref := result[1]
secretRefs[k] = tftypes.NewValue(tftypes.String, ref)
delete(envVariables, k)
}
}

regexPattern := "{{\\s?%s\\s?}}"
// we are going to first loop through all the input secrets
// find and replace this secret in all the rest of the env variables and secrets
for key, secret := range secrets {
Expand Down Expand Up @@ -157,7 +178,7 @@ func (d *dataEnvVariables) Read(ctx context.Context, config map[string]tftypes.V
}

// calculate the unique id for this data source based on a hash of the resulting env variables and secrets
id := d.HashFromValues(envVariables, secrets)
id := d.HashFromValues(envVariables, secrets, secretRefs)

tflog.Debug(ctx, "id", id)
tflog.Debug(ctx, "env_variables", envVariables)
Expand All @@ -169,17 +190,21 @@ func (d *dataEnvVariables) Read(ctx context.Context, config map[string]tftypes.V
"input_secrets": tftypes.NewValue(tftypes.Map{ElementType: tftypes.String}, inputSecrets),
"env_variables": tftypes.NewValue(tftypes.Map{ElementType: tftypes.String}, envVariables),
"secrets": tftypes.NewValue(tftypes.Map{ElementType: tftypes.String}, secrets),
"secret_refs": tftypes.NewValue(tftypes.Map{ElementType: tftypes.String}, secretRefs),
}, nil, nil
}

func (d *dataEnvVariables) HashFromValues(envVariables, secrets map[string]tftypes.Value) string {
func (d *dataEnvVariables) HashFromValues(envVariables, secrets, secretRefs map[string]tftypes.Value) string {
hashString := ""
for k, v := range envVariables {
hashString += fmt.Sprintf("%s=%s;", k, extractStringFromTfValue(v))
}
for k, v := range secrets {
hashString += fmt.Sprintf("%s=%s;", k, extractStringFromTfValue(v))
}
for k, v := range secretRefs {
hashString += fmt.Sprintf("%s=%s;", k, extractStringFromTfValue(v))
}

sum := sha256.Sum256([]byte(hashString))
return fmt.Sprintf("%x", sum)
Expand Down
9 changes: 7 additions & 2 deletions internal/provider/data_env_variables_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,19 @@ import (
)

func TestDataVariables(t *testing.T) {
arn := "arn:aws:secretsmanager:us-east-1:522657839841:secret:scarlet-eagle-kvoty/conn_url-lPd8oL"
ssickles marked this conversation as resolved.
Show resolved Hide resolved

checks := resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("data.ns_env_variables.this", "input_env_variables.%", "6"),
resource.TestCheckResourceAttr("data.ns_env_variables.this", "input_env_variables.%", "7"),
resource.TestCheckResourceAttr("data.ns_env_variables.this", "input_secrets.%", "1"),
resource.TestCheckResourceAttr("data.ns_env_variables.this", "env_variables.%", "5"),
resource.TestCheckResourceAttr("data.ns_env_variables.this", "env_variables.FEATURE_FLAG_0115", "true"),
resource.TestCheckResourceAttr("data.ns_env_variables.this", "env_variables.IDENTIFIER", "primary.acme-api.dev"),
resource.TestCheckResourceAttr("data.ns_env_variables.this", "secrets.%", "2"),
resource.TestCheckResourceAttr("data.ns_env_variables.this", "secrets.POSTGRES_URL", "postgres://user:pass@host:port/db"),
resource.TestCheckResourceAttr("data.ns_env_variables.this", "secrets.DATABASE_URL", "postgres://user:pass@host:port/db"),
resource.TestCheckResourceAttr("data.ns_env_variables.this", "secret_refs.%", "1"),
resource.TestCheckResourceAttr("data.ns_env_variables.this", "secret_refs.VAR_WITH_REF", arn),
)

t.Run("sets up attributes properly hard-coded", func(t *testing.T) {
Expand All @@ -32,12 +36,13 @@ data "ns_env_variables" "this" {
FEATURE_FLAG_0115 = "true"
DATABASE_URL = "{{POSTGRES_URL}}"
IDENTIFIER = "{{ NULLSTONE_STACK }}.{{ NULLSTONE_BLOCK }}.{{ NULLSTONE_ENV }}"
VAR_WITH_REF = "{{ secret(%s) }}"
}
input_secrets = {
POSTGRES_URL = "postgres://user:pass@host:port/db"
}
}
`)
`, arn)
getNsConfig, _ := mockNs(nil)
getTfeConfig, _ := mockTfe(nil)

Expand Down
17 changes: 15 additions & 2 deletions internal/provider/data_secret_keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,18 +92,31 @@ func (d *dataSecretKeys) Read(ctx context.Context, config map[string]tftypes.Val
tflog.Debug(ctx, "input_env_variables", inputEnvVariables)
tflog.Debug(ctx, "input_secret_keys", inputSecretKeys)

// make sure we copy these so our changes below don't affect the original values
envVariables := copyMap(inputEnvVariables)

// first pull out any env variables that contain refs to secrets in their interpolation
refRegexPattern := "{{\\s?secret\\((.+)\\)\\s?}}"
ssickles marked this conversation as resolved.
Show resolved Hide resolved
regex := regexp.MustCompile(refRegexPattern)
for k, v := range envVariables {
// if extractStringFromTfValue(v) contains the regex pattern, and the interpolation is set to `secret(arn)`
// then we need to skip this replacement and output the arn as a secret_ref
if regex.MatchString(extractStringFromTfValue(v)) {
delete(envVariables, k)
}
}

// make sure we copy these so our changes below don't affect the original values
secretKeys := copySet(inputSecretKeys)

regexPattern := "{{\\s?%s\\s?}}"

// loop through and determine if any of the environment variables contain interpolation using any of the secret keys
// if they do, add their keys to the final set of secret keys
added := map[string]bool{}
for _, secretKey := range inputSecretKeys {
regex := regexp.MustCompile(fmt.Sprintf(regexPattern, extractStringFromTfValue(secretKey)))
// first try and replace in the env variables
for k, v := range inputEnvVariables {
for k, v := range envVariables {
// don't add the key more than once, the "added" map keeps track of whether it has already been added
if added[k] {
continue
Expand Down
11 changes: 9 additions & 2 deletions internal/provider/data_secret_keys_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,16 @@ import (
)

func TestSecretKeys(t *testing.T) {
arn := "arn:aws:secretsmanager:us-east-1:522657839841:secret:scarlet-eagle-kvoty/conn_url-lPd8oL"
ssickles marked this conversation as resolved.
Show resolved Hide resolved

checks := resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("data.ns_secret_keys.this", "input_env_variables.%", "7"),
resource.TestCheckResourceAttr("data.ns_secret_keys.this", "input_env_variables.%", "8"),
resource.TestCheckResourceAttr("data.ns_secret_keys.this", "input_secret_keys.#", "2"),
resource.TestCheckResourceAttr("data.ns_secret_keys.this", "secret_keys.#", "4"),
resource.TestCheckResourceAttr("data.ns_secret_keys.this", "secret_keys.0", "DATABASE_URL"),
resource.TestCheckResourceAttr("data.ns_secret_keys.this", "secret_keys.1", "DUPLICATE_TEST"),
resource.TestCheckResourceAttr("data.ns_secret_keys.this", "secret_keys.2", "POSTGRES_URL"),
resource.TestCheckResourceAttr("data.ns_secret_keys.this", "secret_keys.3", "SECRET_KEY_BASE"),
)

t.Run("sets up attributes properly hard-coded", func(t *testing.T) {
Expand All @@ -28,13 +34,14 @@ data "ns_secret_keys" "this" {
DATABASE_URL = "{{POSTGRES_URL}}"
IDENTIFIER = "{{ NULLSTONE_STACK }}.{{ NULLSTONE_BLOCK }}.{{ NULLSTONE_ENV }}"
DUPLICATE_TEST = "{{ SECRET_KEY_BASE }}{{ POSTGRES_URL }}"
VAR_WITH_REF = "{{ secret(%s) }}"
}
input_secret_keys = [
"POSTGRES_URL",
"SECRET_KEY_BASE"
]
}
`)
`, arn)
getNsConfig, _ := mockNs(nil)
getTfeConfig, _ := mockTfe(nil)

Expand Down