How to use Terraform to store a new secret in AWS Secrets Manager using already KMS-encrypted string?

Viewed 2811

I need to write Git-revisioned Terraform code to put a secret string into AWS Secrets Manager. Given a secret string in a textfile:

% cat /tmp/plaintext-password
my-super-secret-password

I am able to make an encrypted version of it using a KMS key:

# Prints base64-encoded, encrypted string.
aws kms encrypt --key-id my_kms_uuid --plaintext fileb:///tmp/plaintext-password --output text --query CiphertextBlob
# abcdef...123456789/==

What Terraform code can be written to get that base64 string in AWS Secrets Manager, such that AWS knows it was encrypted with my_kms_uuid? I have tried the following:

resource "aws_secretsmanager_secret" "testing-secrets-secret" {
  name = "secret-for-testing"
  kms_key_id = "<my_kms_uuid>"
}

resource "aws_secretsmanager_secret_version" "testing-secrets-version" {
  secret_string = "abcdef...123456789/=="
}

The problem is I can't figure out a way to tell AWS Secrets Manager that the string is already encrypted by KMS so it doesn't have to encrypt it again. Can this be done?

2 Answers

If your goal is to keep secret values out of the statefile, then you have two choices:

  1. Encrypt the secret outside of Terraform, then store the encrypted value in Secrets Manager.

    This will force all consumers of the secret to decrypt it before use. Since an encrypted secret includes the CMK used to encrypt it, there's no need for you to separately track the key ID.

    There are several drawbacks to this approach. For one thing, you have to do two steps to use any secret: retrieve it and decrypt it. If you use ECS, you can't provide the name of the secret and let ECS to provide the decrypted value to your container.

    A bigger drawback is that it can be very easy to forget which CMK is used for which secret, and accidentally delete the CMK (at which point the secret becomes unusable). Related is knowing which permissions to grant to the consumers, especially if you have a lot of CMKs.

  2. Create the secret inside Terraform, and set its value manually.

    This keeps the actual value in Secrets Manager, so you don't need to use two steps to decrypt it.

    It is possible to use local-exec to generate the secret within the Terraform configuration: write a script that generates random data and then invokes the AWS CLI to store the value. However, this technique is more frequently used for things like SSH private keys that are created outside of the Terraform provisioning process.

Better than either of these solutions is to store your statefile somewhere that it isn't generally accessible. There are a bunch of backends that can do this for you.

When encrypting a secret string with KMS, the key that was used is actually specified within the output of the resulting ciphertext. This means we can do this the following way:

  1. Encrypt the secret string with an existing KMS key in AWS aws kms encrypt --key-id arn:aws:kms:us-east-1:<account_id>:key/mrk-blahblahblah --plaintext fileb://<(printf 'SOME_SECRET_TEXT') --output text --query CiphertextBlob --region us-east-1
  2. Then use aws_kms_secrets to decrypt it within Terraform (something like this).
  3. Then you push the decrypted secret up into AWS Secrets Manager using aws_secretsmanager_secret_version.

The net result is only the encoded secret is kept in version control, but the password that's actually stored in Secrets Manager is the decoded string.

Related