Skip to content

Commit

Permalink
feat: support secret reference in resource defs
Browse files Browse the repository at this point in the history
  • Loading branch information
delca85 committed Oct 12, 2023
1 parent 8d4cf55 commit f21328e
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 6 deletions.
5 changes: 3 additions & 2 deletions docs/resources/resource_definition.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,9 @@ Read-Only:

Optional:

- `secrets` (Map of String, Sensitive, Deprecated) Secrets section of the data set. Deprecated in favour of secrets_string.
- `secrets_string` (String) JSON encoded secret data set. Passed around as-is.
- `secret_refs` (String) JSON encoded secrets section of the data set. They can hold sensitive information that will be stored in the primary organization secret store and replaced with the secret store paths when sent outside, or secret references stored in a defined secret store. Can't be used together with secrets.
- `secrets` (Map of String, Sensitive, Deprecated) Secrets section of the data set. Deprecated in favour of secrets_string. Can't be used together with secret_refs.
- `secrets_string` (String) JSON encoded secret data set. Passed around as-is. Can't be used together with secret_refs.
- `values` (Map of String, Deprecated) Values section of the data set. Passed around as-is. Deprecated in favour of values_string.
- `values_string` (String) JSON encoded input data set. Passed around as-is.

Expand Down
36 changes: 34 additions & 2 deletions internal/provider/resource_definition_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ type DefinitionResourceDriverInputsModel struct {

ValuesString types.String `tfsdk:"values_string"`
SecretsString types.String `tfsdk:"secrets_string"`
SecretRefs types.String `tfsdk:"secret_refs"`
}

// DefinitionResourceCriteriaModel describes the resource data model.
Expand Down Expand Up @@ -145,21 +146,32 @@ func (r *ResourceDefinitionResource) Schema(ctx context.Context, req resource.Sc
},
},
"secrets": schema.MapAttribute{
MarkdownDescription: "Secrets section of the data set. Deprecated in favour of secrets_string.",
MarkdownDescription: "Secrets section of the data set. Deprecated in favour of secrets_string. Can't be used together with secret_refs.",
ElementType: types.StringType,
Optional: true,
Sensitive: true,
DeprecationMessage: "Use secrets_string instead",
},
"secrets_string": schema.StringAttribute{
MarkdownDescription: "JSON encoded secret data set. Passed around as-is.",
MarkdownDescription: "JSON encoded secret data set. Passed around as-is. Can't be used together with secret_refs.",
Optional: true,
Validators: []validator.String{
stringvalidator.ConflictsWith(path.Expressions{
path.MatchRelative().AtParent().AtName("secrets"),
}...),
},
},
"secret_refs": schema.StringAttribute{
MarkdownDescription: "JSON encoded secrets section of the data set. They can hold sensitive information that will be stored in the primary organization secret store and replaced with the secret store paths when sent outside, or secret references stored in a defined secret store. Can't be used together with secrets.",
Optional: true,
Computed: true,
Validators: []validator.String{
stringvalidator.ConflictsWith(path.Expressions{
path.MatchRelative().AtParent().AtName("secrets_string"),
}...),
},
PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()},
},
},
},
"criteria": schema.SetNestedAttribute{
Expand Down Expand Up @@ -372,6 +384,7 @@ func parseResourceDefinitionResponse(ctx context.Context, driverInputSchema map[
data.DriverInputs = &DefinitionResourceDriverInputsModel{
Secrets: types.MapNull(types.StringType),
SecretsString: types.StringNull(),
SecretRefs: types.StringNull(),
}
}

Expand All @@ -395,6 +408,17 @@ func parseResourceDefinitionResponse(ctx context.Context, driverInputSchema map[
}
}

if data.DriverInputs != nil && data.DriverInputs.SecretRefs.IsUnknown() {
if driverInputs.SecretRefs == nil {
data.DriverInputs.SecretRefs = types.StringNull()
} else {
b, err := json.Marshal(driverInputs.SecretRefs)
if err != nil {
diags.AddError(HUM_API_ERR, fmt.Sprintf("Failed to marshal secret_refs: %s", err.Error()))
}
data.DriverInputs.SecretRefs = types.StringValue(string(b))
}
}
return diags
}

Expand Down Expand Up @@ -529,6 +553,7 @@ func driverInputsFromModel(ctx context.Context, inputSchema map[string]interface
driverInputs := &client.ValuesSecretsRefsRequest{}

var secrets map[string]interface{}
var secretRefs map[string]interface{}
var secretsDiag diag.Diagnostics

if !data.DriverInputs.Secrets.IsNull() {
Expand All @@ -538,11 +563,18 @@ func driverInputsFromModel(ctx context.Context, inputSchema map[string]interface
if err := json.Unmarshal([]byte(data.DriverInputs.SecretsString.ValueString()), &secrets); err != nil {
secretsDiag.AddError(HUM_INPUT_ERR, fmt.Sprintf("Failed to unmarshal secrets_string: %s", err.Error()))
}
} else if !data.DriverInputs.SecretRefs.IsUnknown() {
if err := json.Unmarshal([]byte(data.DriverInputs.SecretRefs.ValueString()), &secretRefs); err != nil {
secretsDiag.AddError(HUM_INPUT_ERR, fmt.Sprintf("Failed to unmarshal secret_refs: %s", err.Error()))
}
}
diags.Append(secretsDiag...)
if secrets != nil {
driverInputs.Secrets = &secrets
}
if secretRefs != nil {
driverInputs.SecretRefs = &secretRefs
}

var values map[string]interface{}
var valuesDiag diag.Diagnostics
Expand Down
131 changes: 129 additions & 2 deletions internal/provider/resource_definition_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package provider
import (
"context"
"fmt"
"os"
"testing"

"github.com/hashicorp/terraform-plugin-framework/types"
Expand Down Expand Up @@ -119,6 +120,48 @@ func TestAccResourceDefinition(t *testing.T) {
},
resourceAttrNameUpdateValue2: "test-2",
},
{
name: "S3 static - secret refs",
configCreate: func() string {
return testAccResourceDefinitionS3taticResourceWithSecretRefs("accessKeyIdPath1", "secretAccessKeyPath1")
},
resourceAttrNameIDValue: "s3-test-with-secrets",
resourceAttrNameUpdateKey: "driver_inputs.secret_refs",
resourceAttrNameUpdateValue1: "{\"aws_access_key_id\":{\"ref\":\"accessKeyIdPath1\",\"store\":\"external-secret-store\",\"version\":\"1\"},\"aws_secret_access_key\":{\"ref\":\"secretAccessKeyPath1\",\"store\":\"external-secret-store\",\"version\":\"1\"}}",
resourceAttrName: "humanitec_resource_definition.s3_test_with_secrets",
configUpdate: func() string {
return testAccResourceDefinitionS3taticResourceWithSecretRefs("accessKeyIdPath2", "secretAccessKeyPath2")
},
resourceAttrNameUpdateValue2: "{\"aws_access_key_id\":{\"ref\":\"accessKeyIdPath2\",\"store\":\"external-secret-store\",\"version\":\"1\"},\"aws_secret_access_key\":{\"ref\":\"secretAccessKeyPath2\",\"store\":\"external-secret-store\",\"version\":\"1\"}}",
},
{
name: "S3 static - secret ref set values",
configCreate: func() string {
return testAccResourceDefinitionS3taticResourceWithSecretRefValues("accessKeyId1", "secretAccessKey1")
},
resourceAttrNameIDValue: "s3-test-with-secrets",
resourceAttrNameUpdateKey: "driver_inputs.secret_refs",
resourceAttrNameUpdateValue1: "{\"aws_access_key_id\":{\"value\":\"accessKeyId1\"},\"aws_secret_access_key\":{\"value\":\"secretAccessKey1\"}}",
resourceAttrName: "humanitec_resource_definition.s3_test_with_secrets",
configUpdate: func() string {
return testAccResourceDefinitionS3taticResourceWithSecretRefValues("accessKeyId2", "secretAccessKey2")
},
resourceAttrNameUpdateValue2: "{\"aws_access_key_id\":{\"value\":\"accessKeyId2\"},\"aws_secret_access_key\":{\"value\":\"secretAccessKey2\"}}",
},
{
name: "S3 static - secrets",
configCreate: func() string {
return testAccResourceDefinitionS3taticResourceWithSecrets("accessKeyId1", "secretAccessKey1")
},
resourceAttrNameIDValue: "s3-test-with-secrets",
resourceAttrNameUpdateKey: "driver_inputs.secret_refs",
resourceAttrNameUpdateValue1: fmt.Sprintf("{\"aws_access_key_id\":{\"ref\":\"%s/aws_access_key_id/.value\",\"store\":\"humanitec\"},\"aws_secret_access_key\":{\"ref\":\"%s/aws_secret_access_key/.value\",\"store\":\"humanitec\"}}", getDefinitionSecretPath("s3-test-with-secrets"), getDefinitionSecretPath("s3-test-with-secrets")),
resourceAttrName: "humanitec_resource_definition.s3_test_with_secrets",
configUpdate: func() string {
return testAccResourceDefinitionS3taticResourceWithSecrets("accessKeyId2", "secretAccessKey2")
},
resourceAttrNameUpdateValue2: fmt.Sprintf("{\"aws_access_key_id\":{\"ref\":\"%s/aws_access_key_id/.value\",\"store\":\"humanitec\"},\"aws_secret_access_key\":{\"ref\":\"%s/aws_secret_access_key/.value\",\"store\":\"humanitec\"}}", getDefinitionSecretPath("s3-test-with-secrets"), getDefinitionSecretPath("s3-test-with-secrets")),
},
}

for _, tc := range tests {
Expand All @@ -140,7 +183,7 @@ func TestAccResourceDefinition(t *testing.T) {
ResourceName: tc.resourceAttrName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"driver_inputs.secrets", "force_delete"},
ImportStateVerifyIgnore: []string{"driver_inputs.secrets_string", "driver_inputs.secret_refs", "force_delete"},
},
// Update and Read testing
{
Expand Down Expand Up @@ -291,6 +334,84 @@ resource "humanitec_resource_definition" "provision_test" {
`, matchDependents)
}

func testAccResourceDefinitionS3taticResourceWithSecretRefs(awsAccessKeyIDPath, awsSecretAccessKeyPath string) string {
return fmt.Sprintf(`
resource "humanitec_resource_definition" "s3_test_with_secrets" {
id = "s3-test-with-secrets"
name = "s3-test-with-secrets"
type = "s3"
driver_type = "humanitec/static"
driver_inputs = {
values_string = jsonencode({
"bucket" = "test-bucket"
"region" = "us-east-1"
})
secret_refs = jsonencode({
"aws_access_key_id" = {
"ref" = "%s"
"store" = "external-secret-store"
"version" = "1"
}
"aws_secret_access_key" = {
"ref" = "%s"
"store" = "external-secret-store"
"version" = "1"
}
})
}
}
`, awsAccessKeyIDPath, awsSecretAccessKeyPath)
}

func testAccResourceDefinitionS3taticResourceWithSecretRefValues(awsAccessKeyIDValue, awsSecretAccessKeyValue string) string {
return fmt.Sprintf(`
resource "humanitec_resource_definition" "s3_test_with_secrets" {
id = "s3-test-with-secrets"
name = "s3-test-with-secrets"
type = "s3"
driver_type = "humanitec/static"
driver_inputs = {
values_string = jsonencode({
"bucket" = "test-bucket"
"region" = "us-east-1"
})
secret_refs = jsonencode({
"aws_access_key_id" = {
"value" = "%s"
}
"aws_secret_access_key" = {
"value" = "%s"
}
})
}
}
`, awsAccessKeyIDValue, awsSecretAccessKeyValue)
}

func testAccResourceDefinitionS3taticResourceWithSecrets(awsAccessKeyIDValue, awsSecretAccessKeyValue string) string {
return fmt.Sprintf(`
resource "humanitec_resource_definition" "s3_test_with_secrets" {
id = "s3-test-with-secrets"
name = "s3-test-with-secrets"
type = "s3"
driver_type = "humanitec/static"
driver_inputs = {
values_string = jsonencode({
"bucket" = "test-bucket"
"region" = "us-east-1"
})
secrets_string = jsonencode({
"aws_access_key_id" = "%s"
"aws_secret_access_key" = "%s"
})
}
}
`, awsAccessKeyIDValue, awsSecretAccessKeyValue)
}

func TestAccResourceDefinitionLegacyValues(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Expand Down Expand Up @@ -340,7 +461,7 @@ resource "humanitec_resource_definition" "s3_legacy_test" {
`, region)
}

func TestAccResourceDefinitionWithDefinition(t *testing.T) {
func TestAccResourceDefinitionWithCriteria(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Expand All @@ -363,6 +484,7 @@ func TestAccResourceDefinitionWithDefinition(t *testing.T) {
ImportStateVerifyIgnore: []string{
"criteria", // won't be imported as the provider can't determine if the field is set
"driver_inputs.secrets",
"driver_inputs.secret_refs",
"force_delete",
},
},
Expand Down Expand Up @@ -704,3 +826,8 @@ func TestParseMapInput_UnexpectedType(t *testing.T) {
_, diags := parseMapInput(input, schema, "test")
assert.True(diags.HasError())
}

func getDefinitionSecretPath(defID string) string {
orgID := os.Getenv("HUMANITEC_ORG_ID")
return fmt.Sprintf("orgs/%s/resources/defs/%s/driver_secrets", orgID, defID)
}

0 comments on commit f21328e

Please sign in to comment.