Terraform AWS Provider: SecretsManager can't apply because version was deleted - amazon-web-services

We have an AWS SecretsManager Secret that was created once. That secret will be updated by an external job every hour.
I have the problem that sometimes the terraform plan/apply fails with the following message:
AWS Provider 2.48
Error: Error refreshing state: 1 error occurred:
* module.xxx.xxx: 1 error occurred:
* module.xxx.aws_secretsmanager_secret_version.xxx:
aws_secretsmanager_secret_version.xxx: error reading Secrets Manager Secret Version: InvalidRequestException: You can't perform this operation on secret version 68AEABC3-34BE-4723-8BF5-469A44F9B1D9 because it was deleted.
We've tried two solutions:
1) Force delete the whole secret via aws cli, but this has the side effect that one of our dependend resources will also be recreated (ecs template definition depends on that secret). This works, but we do not want the side effect of recreating the ecs thing.
2) Manually edit the backend .tfstate file and set the current AWS secret version. Then run the plan again.
Both solution seem to be hacky in a way. What is the best way to solve this issue ?

You can use terraform import to reconcile the state difference before you run a plan or apply.
In your case, this would look like:
terraform import module.xxx.aws_secretsmanager_secret_version.xxx arn:aws:secretsmanager:some_region:some_account_id:secret:example-123456|xxxxx-xxxxxxx-xxxxxxx-xxxxx

I think perhaps the problem you are having is that by default AWS tries to "help you" by not letting you delete secrets automatically until 7 days have elapsed. AWS tries the "help you" by telling you they give you a grace period of 7 days to update your "code" that may rely on this. Which makes automation more difficult.
I have worked around this by setting the recovery window period to "0 days", effectively eliminating that grace period that AWS provides.
Then you can have terraform, rename, or delete your secret at will, either manually (via AWS CLI) or via terraform.
You can update an existing secret by putting in this value FIRST. Then change the name of the secret (if you wish to), or delete it (this terraform section) as desired and run the terraform again after the recovery window days = 0 has been applied.
Here is an example:
resource "aws_secretsmanager_secret" "mySecret" {
name = "your secret name"
recovery_window_in_days = "0"
// this is optional and can be set to true | false
lifecycle {
create_before_destroy = true
}
}
*Note, there is also an option to "create before destroy" you can set on the lifecyle.
https://www.terraform.io/docs/configuration/resources.html

Also, you can use the terraform resource to update the secret values like this:
This example will set the secret values once and then tell terraform to ignore any changes made to the values (username, password in this example) after the initial creation.
If you remove the lifecyle section, then terraform will keep track of whether or not the secret values themselves have changed. If they have changed they would revert back to the value in the terraform state.
If you store your tfstate files in an s3 protected bucket that is safer than not doing so, because they are plaintext in the statefile, so anyone with access to your terraform state file could see your secret values.
I would suggest: 1) figuring out what is deleting your secrets unexpectedly? 2) having your "external job" be a terraform bash script to update the values using a resource as in the example below.
Hope this gives you some ideas.
resource "aws_secretsmanager_secret_version" "your-secret-data" {
secret_id = aws_secretsmanager_secret.your-secret.id
secret_string = <<-EOF
{
"username": "usernameValue",
"password": "passwordValue"
}
EOF
// ignore any updates to the initial values above done after creation.
lifecycle {
ignore_changes = [
secret_string
]
}
}

Related

Terraform - Manage destroy of everything except the S3 bucket

We create all our AWS resources using Terraform and we are having issues with our S3 bucket.
We are in a development environnement so sometimes we wants to destroy everything and recreate everything except the S3 Bucket.
We tried to implement the following attribute for our S3 Bucket :
resource "aws_s3_bucket" "bucket" {
bucket = "${var.name}-${var.environment}-data"
lifecycle {
prevent_destroy = true
}
}
It seems like the prevent_destroy attribute is not working as we thought it would work. Instead of skipping the deletion of the bucket and terminate the terraform destroy with a success state, it fails instead (as if this attribute tells terraform to fail on purpose).
I've found out similar conclusions already on stackoverflow but what would be the best way to avoid that issue and also the way that would be the best practice please ?
We use also github actions so we have thought of using it to create the bucket but if there's a solution using terraform, it will be easier for us (as there are other resources that are linked to the bucket id).
Thanks in advance !
prevent_destroy is used as a safety measure to ensure that the deletion of the resource does not occur. It is expected that it errors out. From the docs:
This meta-argument, when set to true, will cause Terraform to reject with an error any plan that would destroy the infrastructure object associated with the resource
so you shouldn't be using this meta-argument for your purposes.
Instead, if you want to delete everything except the bucket, you will need to remove the bucket from the terraform state before running the destroy operation, e.g.:
terraform state rm aws_s3_bucket.bucket
terraform destroy
for your next deployment, you will then need to import the bucket back into the terraform state before running the apply:
terraform import aws_s3_bucket.bucket bucket-name
terraform apply

AWS account creation using terraform

I am trying to create new aws account within our AWS org, but I am still getting no changes after terraform plan:
"No changes. Your infrastructure matches the configuration.
Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed."
Am I missing something? This is the code:
resource "aws_organizations_account" "new_aws_member_account" {
name = "XXX"
email = "XXX#XXX"
iam_user_access_to_billing = "ALLOW"
}
I already tried to deploy new IAM policy (within AWS org account) and there was no problem, but I just can't create new account using this code, I probably missed something, but don't know what.
Our AWS org is created manually using AWS console, so not via terraform, but this shouldn't be a problem or yes?
Can you help please?

How to tell Terraform to skip the secret manager resource if it exists?

The idea is that I want to use Terraform resource aws_secretsmanager_secret to create only three secrets (not workspace-specified secret), one for the dev environment, one for preprod and the third one for production env.
Something like:
resource "aws_secretsmanager_secret" "dev_secret" {
name = "example-secret-dev"
}
resource "aws_secretsmanager_secret" "preprod_secret" {
name = "example-secret-preprod"
}
resource "aws_secretsmanager_secret" "prod_secret" {
name = "example-secret-prod"
}
But after creating them, I don't want to overwrite them every time I run 'Terraform apply', is there a way to tell Terraform if any of the secrets exist, skip the creation of the secret and do not overwrite?
I had a look at this page but still doesn't have a clear solution, any suggestion will be appreciated.
It will not overwrite the secret if you create it manually in the console or using AWS SDK. The aws_secretsmanager_secret creates only the secret, but not its value. To set value you have to use aws_secretsmanager_secret_version.
Anyway, this is something you can easily test yourself. Just run your code with a secret, update its value in AWS console, and re-run terraform apply. You should see no change in the secret's value.
You could have Terraform generate random secret values for you using:
data "aws_secretsmanager_random_password" "dev_password" {
password_length = 16
}
Then create the secret metadata using:
resource "aws_secretsmanager_secret" "dev_secret" {
name = "dev-secret"
recovery_window_in_days = 7
}
And then by creating the secret version:
resource "aws_secretsmanager_secret_version" "dev_sv" {
secret_id = aws_secretsmanager_secret.dev_secret.id
secret_string = data.aws_secretsmanager_random_password.dev_password.random_password
lifecycle {
ignore_changes = [secret_string, ]
}
}
Adding the 'ignore_changes' lifecycle block to the secret version will prevent Terraform from overwriting the secret once it has been created. I tested this just now to confirm that a new secret with a new random value will be created, and subsequent executions of terraform apply do not overwrite the secret.

Rotating Keys for the User Created Via Terraform

I have a IAM user that is created by Terraform. Keys are stored in Hashicrop Vault and apps read them from there.
I have developed ansible code/bash scripts to rotate the keys periodically successfully.
But the issue is terraform doesn't like when the keys are rotated. Whenever we try to run terraform it tries to re-create the key
Is there any way to manage the key rotation via terraform? or can we ignore this in terraform. Any help with examples would be really helpful.
Key rotation in terraform is possible by using terraform apply -replace=<resource address>, which replaces the resource immediately or terraform taint <resource address>, which replaces the resource on the next apply for version below v0.15.2. See https://www.terraform.io/docs/cli/commands/taint.html for more information.
When using these commands it makes sense to set the lifecycle of the resources to replace to create_before_destroy to avoid downtime. So in the case of AWS access keys, that would be
resource "aws_iam_access_key" "my_user" {
user = "my_user_name"
lifecycle {
create_before_destroy = true
}
}
Given this configuration one can simply run terraform apply -replace=aws_iam_access_key.my_user to rotate the keys. One only has to make sure that downstream applications that use the keys take notice of the changes and are restarted if necessary to make use of the new keys.
We have managed to solve the problem by removing key generation initially via terraform when the user is created.
We are using some ansible and bash scripts to now generate and rotate keys and then vault api to update secrets in Vault.

Terraform command to list existing AWS resources as a Hello World

I have the AWS CLI installed on my Windows computer, and running this command "works" exactly like I want it to.
aws ec2 describe-images
I get the following output, which is exactly what I want to see, because although I have access to AWS through my corporation (e.g. to check code into CodeCommit), I can see in the AWS web console for EC2 that I don't have permission to list running instances:
An error occurred (UnauthorizedOperation) when calling the DescribeImages operation: You are not authorized to perform this operation.
I've put terraform.exe onto my computer as well, and I've created a file "example.tf" that contains the following:
provider "aws" {
region = "us-east-1"
}
I'd like to issue some sort of Terraform command that would yell at me, explaining that my AWS account is not allowed to list Amazon instances.
Most Hello World examples involve using terraform plan against a resource to do an "almost-write" against AWS.
Personally, however, I always feel more comfortable knowing that things are behaving as expected with something a bit more "truly read-only." That way, I really know the round-trip to AWS worked but I didn't modify any of my corporation's state.
There's a bunch of stuff on the internet about "data sources" and their "aws_ami" or "aws_instances" flavors, but I can't find anything that tells me how to actually use it with a Terraform command for a simple print()-type interaction (the way it's obvious that, say, "resources" go with the "terraform plan" and "terraform apply" commands).
Is there something I can do with Terraform commands to "hello world" an attempt at listing all my organization's EC2 servers and, accordingly, watching AWS tell me to buzz off because I'm not authorized?
You can use the data source for AWS instances. You create a data source similar to the below:
data "aws_instances" "test" {
instance_tags = {
Role = "HardWorker"
}
filter {
name = "instance.group-id"
values = ["sg-12345678"]
}
instance_state_names = ["running", "stopped"]
}
This will attempt to perform a read action listing your EC2 instances designated by the filter you put in the config. This will also utilize the IAM associated with the Terraform user you are performing the terraform plan with. This will result in the error you described regarding lack of authorization, which is your stated goal. You should modify the filter to target your organization's EC2 instances.