Skip to content

Commit

Permalink
Feat: Allow Certificate and Private Key Extensions to be Overridden (#3)
Browse files Browse the repository at this point in the history
* Allow certificate and private key extensions to be overridden; add test for extension override, fix order of variables in assert.Equal in test suite.

* Auto Format

Co-authored-by: cloudpossebot <[email protected]>
  • Loading branch information
korenyoni and cloudpossebot authored Aug 11, 2021
1 parent 19f6c38 commit 4498648
Show file tree
Hide file tree
Showing 12 changed files with 128 additions and 44 deletions.
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,11 +180,11 @@ Available targets:

| Name | Type |
|------|------|
| [aws_secretsmanager_secret.pem](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret) | resource |
| [aws_secretsmanager_secret.certificate](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret) | resource |
| [aws_secretsmanager_secret.private_key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret) | resource |
| [aws_secretsmanager_secret_version.pem](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret_version) | resource |
| [aws_secretsmanager_secret_version.certificate](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret_version) | resource |
| [aws_secretsmanager_secret_version.private_key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret_version) | resource |
| [aws_ssm_parameter.pem](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
| [aws_ssm_parameter.certificate](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
| [aws_ssm_parameter.private_key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
| [tls_private_key.default](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/private_key) | resource |
| [tls_self_signed_cert.default](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/self_signed_cert) | resource |
Expand Down Expand Up @@ -213,7 +213,8 @@ Available targets:
| <a name="input_private_key_ecdsa_curve"></a> [private\_key\_ecdsa\_curve](#input\_private\_key\_ecdsa\_curve) | When `var.cert_key_algorithm` is `ECDSA`, the name of the elliptic curve to use. May be any one of `P224`, `P256`, `P384` or `P521`.<br><br>Ignored if `var.cert_key_algorithm` is not `ECDSA`, or if a preexisting private key is supplied via `var.private_key_contents`.<br><br>Defaults to the `tls` provider default. | `string` | `"P224"` | no |
| <a name="input_private_key_rsa_bits"></a> [private\_key\_rsa\_bits](#input\_private\_key\_rsa\_bits) | When `var.cert_key_algorithm` is `RSA`, the size of the generated RSA key in bits.<br><br>Ignored if `var.cert_key_algorithm` is not `RSA`, or if a preexisting private key is supplied via `var.private_key_contents`.<br><br>Defaults to the `tls` provider default. | `number` | `2048` | no |
| <a name="input_regex_replace_chars"></a> [regex\_replace\_chars](#input\_regex\_replace\_chars) | Regex to replace chars with empty string in `namespace`, `environment`, `stage` and `name`.<br>If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no |
| <a name="input_secret_path_format"></a> [secret\_path\_format](#input\_secret\_path\_format) | The path format to use when writing secrets to the secret store.<br><br>The secret path will be computed as `format(var.secret_path_format, var.name, <secret extension>)`.<br>Thus by default, if `var.name`=`example-self-signed-cert`, then the resulting secret path for the self-signed certificate's<br>PEM file will be `/example-self-signed-cert.pem`.<br><br>This variable can be overridden in order to create more specific secret store paths. | `string` | `"/%s.%s"` | no |
| <a name="input_secret_extensions"></a> [secret\_extensions](#input\_secret\_extensions) | The extensions use when writing secrets to the secret store.<br><br>Please refer to `var.secret_path_format` for information on how secret paths are computed. | <pre>object({<br> certificate = string<br> private_key = string<br> })</pre> | <pre>{<br> "certificate": "pem",<br> "private_key": "key"<br>}</pre> | no |
| <a name="input_secret_path_format"></a> [secret\_path\_format](#input\_secret\_path\_format) | The path format to use when writing secrets to the secret store.<br><br>The certificate secret path will be computed as `format(var.secret_path_format, var.name, var.secret_extensions.certificate)`<br>and the private key path as `format(var.secret_path_format, var.name, var.secret_extensions.private_key)`.<br><br>Thus by default, if `var.name`=`example-self-signed-cert`, then the resulting secret paths for the self-signed certificate's<br>PEM file and private key will be `/example-self-signed-cert.pem` and `/example-self-signed-cert.key`, respectively.<br><br>This variable can be overridden in order to create more specific secret store paths. | `string` | `"/%s.%s"` | no |
| <a name="input_secrets_store_base64_enabled"></a> [secrets\_store\_base64\_enabled](#input\_secrets\_store\_base64\_enabled) | Enable or disable base64 encoding of secrets before writing them to the secrets store. | `bool` | `false` | no |
| <a name="input_secrets_store_enabled"></a> [secrets\_store\_enabled](#input\_secrets\_store\_enabled) | Enable or disable writing to the secrets store. | `bool` | `true` | no |
| <a name="input_secrets_store_kms_key_id"></a> [secrets\_store\_kms\_key\_id](#input\_secrets\_store\_kms\_key\_id) | The KMD Key ID (ARN or ID) to use when encrypting either the AWS SSM Parameters or AWS Secrets Manager Secrets relating to the certificate.<br><br>If not specified, the Amazon-managed Key `alias/aws/ssm` will be used if `var.secrets_store_type` is `SSM`,<br>and `alias/aws/secretsmanager` will be used if `var.secrets_store_type` is `ASM`. | `string` | `null` | no |
Expand Down
10 changes: 5 additions & 5 deletions asm.tf
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
resource "aws_secretsmanager_secret" "pem" {
resource "aws_secretsmanager_secret" "certificate" {
count = local.asm_enabled ? 1 : 0

name = format(var.secret_path_format, module.this.name, "pem")
name = format(var.secret_path_format, module.this.name, var.secret_extensions.certificate)
recovery_window_in_days = var.asm_recovery_window_in_days
kms_key_id = local.secrets_store_kms_key_id

tags = module.this.tags
}

resource "aws_secretsmanager_secret_version" "pem" {
resource "aws_secretsmanager_secret_version" "certificate" {
count = local.asm_enabled ? 1 : 0

secret_id = join("", aws_secretsmanager_secret.pem.*.name)
secret_id = join("", aws_secretsmanager_secret.certificate.*.name)
secret_string = var.secrets_store_base64_enabled ? base64encode(local.tls_certificate) : local.tls_certificate
}

resource "aws_secretsmanager_secret" "private_key" {
count = local.asm_enabled ? 1 : 0

name = format(var.secret_path_format, module.this.name, "key")
name = format(var.secret_path_format, module.this.name, var.secret_extensions.private_key)
recovery_window_in_days = var.asm_recovery_window_in_days
kms_key_id = local.secrets_store_kms_key_id

Expand Down
9 changes: 5 additions & 4 deletions docs/terraform.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@

| Name | Type |
|------|------|
| [aws_secretsmanager_secret.pem](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret) | resource |
| [aws_secretsmanager_secret.certificate](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret) | resource |
| [aws_secretsmanager_secret.private_key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret) | resource |
| [aws_secretsmanager_secret_version.pem](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret_version) | resource |
| [aws_secretsmanager_secret_version.certificate](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret_version) | resource |
| [aws_secretsmanager_secret_version.private_key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/secretsmanager_secret_version) | resource |
| [aws_ssm_parameter.pem](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
| [aws_ssm_parameter.certificate](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
| [aws_ssm_parameter.private_key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
| [tls_private_key.default](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/private_key) | resource |
| [tls_self_signed_cert.default](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/self_signed_cert) | resource |
Expand Down Expand Up @@ -57,7 +57,8 @@
| <a name="input_private_key_ecdsa_curve"></a> [private\_key\_ecdsa\_curve](#input\_private\_key\_ecdsa\_curve) | When `var.cert_key_algorithm` is `ECDSA`, the name of the elliptic curve to use. May be any one of `P224`, `P256`, `P384` or `P521`.<br><br>Ignored if `var.cert_key_algorithm` is not `ECDSA`, or if a preexisting private key is supplied via `var.private_key_contents`.<br><br>Defaults to the `tls` provider default. | `string` | `"P224"` | no |
| <a name="input_private_key_rsa_bits"></a> [private\_key\_rsa\_bits](#input\_private\_key\_rsa\_bits) | When `var.cert_key_algorithm` is `RSA`, the size of the generated RSA key in bits.<br><br>Ignored if `var.cert_key_algorithm` is not `RSA`, or if a preexisting private key is supplied via `var.private_key_contents`.<br><br>Defaults to the `tls` provider default. | `number` | `2048` | no |
| <a name="input_regex_replace_chars"></a> [regex\_replace\_chars](#input\_regex\_replace\_chars) | Regex to replace chars with empty string in `namespace`, `environment`, `stage` and `name`.<br>If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no |
| <a name="input_secret_path_format"></a> [secret\_path\_format](#input\_secret\_path\_format) | The path format to use when writing secrets to the secret store.<br><br>The secret path will be computed as `format(var.secret_path_format, var.name, <secret extension>)`.<br>Thus by default, if `var.name`=`example-self-signed-cert`, then the resulting secret path for the self-signed certificate's<br>PEM file will be `/example-self-signed-cert.pem`.<br><br>This variable can be overridden in order to create more specific secret store paths. | `string` | `"/%s.%s"` | no |
| <a name="input_secret_extensions"></a> [secret\_extensions](#input\_secret\_extensions) | The extensions use when writing secrets to the secret store.<br><br>Please refer to `var.secret_path_format` for information on how secret paths are computed. | <pre>object({<br> certificate = string<br> private_key = string<br> })</pre> | <pre>{<br> "certificate": "pem",<br> "private_key": "key"<br>}</pre> | no |
| <a name="input_secret_path_format"></a> [secret\_path\_format](#input\_secret\_path\_format) | The path format to use when writing secrets to the secret store.<br><br>The certificate secret path will be computed as `format(var.secret_path_format, var.name, var.secret_extensions.certificate)`<br>and the private key path as `format(var.secret_path_format, var.name, var.secret_extensions.private_key)`.<br><br>Thus by default, if `var.name`=`example-self-signed-cert`, then the resulting secret paths for the self-signed certificate's<br>PEM file and private key will be `/example-self-signed-cert.pem` and `/example-self-signed-cert.key`, respectively.<br><br>This variable can be overridden in order to create more specific secret store paths. | `string` | `"/%s.%s"` | no |
| <a name="input_secrets_store_base64_enabled"></a> [secrets\_store\_base64\_enabled](#input\_secrets\_store\_base64\_enabled) | Enable or disable base64 encoding of secrets before writing them to the secrets store. | `bool` | `false` | no |
| <a name="input_secrets_store_enabled"></a> [secrets\_store\_enabled](#input\_secrets\_store\_enabled) | Enable or disable writing to the secrets store. | `bool` | `true` | no |
| <a name="input_secrets_store_kms_key_id"></a> [secrets\_store\_kms\_key\_id](#input\_secrets\_store\_kms\_key\_id) | The KMD Key ID (ARN or ID) to use when encrypting either the AWS SSM Parameters or AWS Secrets Manager Secrets relating to the certificate.<br><br>If not specified, the Amazon-managed Key `alias/aws/ssm` will be used if `var.secrets_store_type` is `SSM`,<br>and `alias/aws/secretsmanager` will be used if `var.secrets_store_type` is `ASM`. | `string` | `null` | no |
Expand Down
16 changes: 16 additions & 0 deletions examples/custom_secrets/custom-suffixes.us-east-1.tfvars
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
region = "us-east-1"

namespace = "eg"

environment = "ue1"

stage = "test"

name = "self-signed-cert-custom-suffixes"

secret_extensions = {
certificate = "crt"
private_key = "key"
}

secret_path_format = "/%s.%s"
1 change: 1 addition & 0 deletions examples/custom_secrets/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ module "self_signed_cert" {
"server_auth"
]

secret_extensions = var.secret_extensions
secret_path_format = var.secret_path_format
secrets_store_type = var.secrets_store_type
secrets_store_enabled = var.secrets_store_enabled
Expand Down
12 changes: 12 additions & 0 deletions examples/custom_secrets/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,18 @@ variable "create_cmk" {
default = false
}

variable "secret_extensions" {
description = "The extensions use when writing secrets to the secret store."
type = object({
certificate = string
private_key = string
})
default = {
certificate = "pem"
private_key = "key"
}
}

variable "secret_path_format" {
description = "The custom secret path to use."
type = string
Expand Down
2 changes: 1 addition & 1 deletion outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ output "certificate_key_path" {

output "certificate_pem_path" {
description = "Secrets store path containing the certificate PEM file."
value = local.secrets_store_enabled ? coalesce(join("", aws_ssm_parameter.pem.*.name), join("", aws_secretsmanager_secret.pem.*.name)) : null
value = local.secrets_store_enabled ? coalesce(join("", aws_ssm_parameter.certificate.*.name), join("", aws_secretsmanager_secret.certificate.*.name)) : null
}

output "certificate_pem" {
Expand Down
6 changes: 3 additions & 3 deletions ssm.tf
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
resource "aws_ssm_parameter" "pem" {
resource "aws_ssm_parameter" "certificate" {
count = local.ssm_enabled ? 1 : 0

name = format(var.secret_path_format, module.this.name, "pem")
name = format(var.secret_path_format, module.this.name, var.secret_extensions.certificate)
type = "SecureString"
key_id = local.secrets_store_kms_key_id
value = var.secrets_store_base64_enabled ? base64encode(local.tls_certificate) : local.tls_certificate
Expand All @@ -12,7 +12,7 @@ resource "aws_ssm_parameter" "pem" {
resource "aws_ssm_parameter" "private_key" {
count = local.ssm_enabled ? 1 : 0

name = format(var.secret_path_format, module.this.name, "key")
name = format(var.secret_path_format, module.this.name, var.secret_extensions.private_key)
type = "SecureString"
key_id = local.secrets_store_kms_key_id
value = var.secrets_store_base64_enabled ? base64encode(local.tls_key) : local.tls_key
Expand Down
8 changes: 4 additions & 4 deletions test/src/examples_complete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,10 @@ func testExamplesCompleteNonCA(t *testing.T) {
terraform.Apply(t, terraformOptions)

certificatePEMPath := terraform.Output(t, terraformOptions, "certificate_pem_path")
assert.Equal(t, certificatePEMPath, "/self-signed-cert.pem")
assert.Equal(t, "/self-signed-cert.pem", certificatePEMPath)

certificateKeyPath := terraform.Output(t, terraformOptions, "certificate_key_path")
assert.Equal(t, certificateKeyPath, "/self-signed-cert.key")
assert.Equal(t, "/self-signed-cert.key", certificateKeyPath)
}

func testExamplesCompleteCA(t *testing.T) {
Expand Down Expand Up @@ -112,8 +112,8 @@ func testExamplesCompleteCA(t *testing.T) {
terraform.Apply(t, terraformOptions)

certificatePEMPath := terraform.Output(t, terraformOptions, "certificate_pem_path")
assert.Equal(t, certificatePEMPath, "/self-signed-cert-ca.pem")
assert.Equal(t, "/self-signed-cert-ca.pem", certificatePEMPath)

certificateKeyPath := terraform.Output(t, terraformOptions, "certificate_key_path")
assert.Equal(t, certificateKeyPath, "/self-signed-cert-ca.key")
assert.Equal(t, "/self-signed-cert-ca.key", certificateKeyPath)
}
47 changes: 41 additions & 6 deletions test/src/examples_custom_secrets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func TestExamplesCustomSecrets(t *testing.T) {
t.Run("SSM", testExamplesCustomSecretsSSM)
t.Run("ASM", testExamplesCustomSecretsASM)
t.Run("CMK", testExamplesCustomSecretsCMK)
t.Run("CustomSuffixes", testExamplesCustomSecretsSuffixes)
}

func testExamplesCustomSecretsNoStore(t *testing.T) {
Expand Down Expand Up @@ -83,10 +84,10 @@ func testExamplesCustomSecretsSSM(t *testing.T) {
terraform.Apply(t, terraformOptions)

certificatePEMPath := terraform.Output(t, terraformOptions, "certificate_pem_path")
assert.Equal(t, certificatePEMPath, "/test-ssm/self-signed-cert-ssm.pem")
assert.Equal(t, "/test-ssm/self-signed-cert-ssm.pem", certificatePEMPath)

certificateKeyPath := terraform.Output(t, terraformOptions, "certificate_key_path")
assert.Equal(t, certificateKeyPath, "/test-ssm/self-signed-cert-ssm.key")
assert.Equal(t, "/test-ssm/self-signed-cert-ssm.key", certificateKeyPath)
}

func testExamplesCustomSecretsASM(t *testing.T) {
Expand Down Expand Up @@ -117,10 +118,10 @@ func testExamplesCustomSecretsASM(t *testing.T) {
terraform.Apply(t, terraformOptions)

certificatePEMPath := terraform.Output(t, terraformOptions, "certificate_pem_path")
assert.Equal(t, certificatePEMPath, "/test-asm/self-signed-cert-asm.pem")
assert.Equal(t, "/test-asm/self-signed-cert-asm.pem", certificatePEMPath)

certificateKeyPath := terraform.Output(t, terraformOptions, "certificate_key_path")
assert.Equal(t, certificateKeyPath, "/test-asm/self-signed-cert-asm.key")
assert.Equal(t, "/test-asm/self-signed-cert-asm.key", certificateKeyPath)
}


Expand Down Expand Up @@ -152,8 +153,42 @@ func testExamplesCustomSecretsCMK(t *testing.T) {
terraform.Apply(t, terraformOptions)

certificatePEMPath := terraform.Output(t, terraformOptions, "certificate_pem_path")
assert.Equal(t, certificatePEMPath, "/test-cmk/self-signed-cert-cmk.pem")
assert.Equal(t, "/test-cmk/self-signed-cert-cmk.pem", certificatePEMPath)

certificateKeyPath := terraform.Output(t, terraformOptions, "certificate_key_path")
assert.Equal(t, certificateKeyPath, "/test-cmk/self-signed-cert-cmk.key")
assert.Equal(t, "/test-cmk/self-signed-cert-cmk.key", certificateKeyPath)
}

func testExamplesCustomSecretsSuffixes(t *testing.T) {
t.Parallel()

rand.Seed(time.Now().UnixNano() + 4) // give a slightly different seed than the other parallel tests

attributes := []string{strconv.Itoa(rand.Intn(100000))}

terraformOptions := &terraform.Options{
// The path to where our Terraform code is located
TerraformDir: "../../examples/custom_secrets",
Upgrade: true,
EnvVars: map[string]string{
"TF_CLI_ARGS": "-state=terraform-custom-suffixes-test.tfstate",
},
// Variables to pass to our Terraform code using -var-file options
VarFiles: []string{"custom-suffixes.us-east-1.tfvars"},
Vars: map[string]interface{}{
"attributes": attributes,
},
}

// At the end of the test, run `terraform destroy` to clean up any resources that were created
defer terraform.Destroy(t, terraformOptions)

// This will run `terraform init` and `terraform apply` and fail the test if there are any errors
terraform.Apply(t, terraformOptions)

certificatePEMPath := terraform.Output(t, terraformOptions, "certificate_pem_path")
assert.Equal(t, "/self-signed-cert-custom-suffixes.crt", certificatePEMPath)

certificateKeyPath := terraform.Output(t, terraformOptions, "certificate_key_path")
assert.Equal(t, "/self-signed-cert-custom-suffixes.key", certificateKeyPath)
}
Loading

0 comments on commit 4498648

Please sign in to comment.