Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Terraform RDS database credentials

I am trying to use AWS secrets manager to declare RDS admin credentials.

  1. Declared credentials in rds.tf in variable RdsAdminCred as key/value pair
  2. Declared secret as well in the same tf file
variable "RdsAminCred" {
    default = {
        username = "dbadmin"
        password = "dbadmin#02avia"
    }
    type = map(string)
}

resource "aws_secretsmanager_secret" "RdsAminCred" {
  name = "RdsAminCred"
}
resource "aws_secretsmanager_secret_version" "RdsAminCred" {
  secret_id     = aws_secretsmanager_secret.RdsAminCred.id
  secret_string = jsonencode(var.RdsAminCred)
}
  1. I am not sure how to use the secret string in the declaration below, to replace the hardcoded value for username and password.
resource "aws_db_instance" "default" {
  identifier            = "testdb"
  allocated_storage    = 20
  storage_type         = "gp2"
  engine               = "mysql"
  engine_version       = "5.7"
  instance_class       = "db.t2.medium"
  name                 = "mydb"
 
  username             = "dbadmin"
  password             = "dbadmin#01avia"

Any help is appreciated..

like image 920
Sudhir Jangam Avatar asked Jan 06 '21 22:01

Sudhir Jangam


4 Answers

I would have a TF config that sets up your secret and stores it in AWS Secrets Manager, like this.

resource "random_password" "master"{
  length           = 16
  special          = true
  override_special = "_!%^"
}

resource "aws_secretsmanager_secret" "password" {
  name = "test-db-password"
}

resource "aws_secretsmanager_secret_version" "password" {
  secret_id = aws_secretsmanager_secret.password.id
  secret_string = random_password.master.result
}

And then in a separate TF config for your database, you can use the secret from AWS Secrets Manager.

data "aws_secretsmanager_secret" "password" {
  name = "test-db-password"

}

data "aws_secretsmanager_secret_version" "password" {
  secret_id = data.aws_secretsmanager_secret.password
}

resource "aws_db_instance" "default" {
  identifier            = "testdb"
  allocated_storage    = 20
  storage_type         = "gp2"
  engine               = "mysql"
  engine_version       = "5.7"
  instance_class       = "db.t2.medium"
  name                 = "mydb"
 
  username             = "dbadmin"
  password             = data.aws_secretsmanager_secret_version.password

In the comments above, Asri Badlah suggested that the password be entered manually in the console. And I guess you can do that. However, that approach does start to get away from the fundamental tenet of IaC - put everything in source control. Of course you don't want to check passwords, private keys or the like into source control. But here, you can see we're not doing that. We populate a secret with one config and consume it with another. This ensures the code can be checked into source control, but the password is not.

In terms of state, it's true that the password will be stored and decipherable in TF state. But if you use proper state management, this shouldn't be an issue. Ideally, you would want to be using remote state, encrypted and with restricted access.

As a final point, I would not use just random_password as Evan Closson suggested. That approach would mean that your database password is 100% managed by Terraform. By using Secrets Manager, your password is managed by a service, which means that you can do other stuff like rotate the password (not shown) and retrieve the password without having to rely on Terraform (e.g. terraform output or cracking open the state file).

like image 156
Clayton Long Avatar answered Nov 15 '22 06:11

Clayton Long


I'd recommend using the random_password resource instead. Then you can reference that in the cluster configuration and secrets manager.

Example:

resource "random_password" "master_password" {
  length  = 16
  special = false
}

resource "aws_rds_cluster" "default" {
  cluster_identifier = "my-cluster"
  
  master_username = "admin"
  master_password = random_password.default_master_password.result

  # other configurations
  # .
  # .
  # .
}

resource "aws_secretsmanager_secret" "rds_credentials" {
  name = "credentials"
}

resource "aws_secretsmanager_secret_version" "rds_credentials" {
  secret_id     = aws_secretsmanager_secret.rds_credentials.id
  secret_string = <<EOF
{
  "username": "${aws_rds_cluster.default.master_username}",
  "password": "${random_password.master_password.result}",
  "engine": "mysql",
  "host": "${aws_rds_cluster.default.endpoint}",
  "port": ${aws_rds_cluster.default.port},
  "dbClusterIdentifier": "${aws_rds_cluster.default.cluster_identifier}"
}
EOF
}
like image 45
Evan Closson Avatar answered Nov 15 '22 08:11

Evan Closson


In your Terraform code, you can use the aws_secretsmanager_secret_version data source to read this secret:

data "aws_secretsmanager_secret_version" "creds" {
  # write your secret name here
  secret_id = "your_secret"
}

parse the secret from JSON, using jsondecode :

locals {
  your_secret = jsondecode(
    data.aws_secretsmanager_secret_version.creds.secret_string
  )
}

Now pass the secret to RDS:

resource "aws_db_instance" "example" {
  engine               = "engine"
  engine_version       = "version"
  instance_class       = "instance"
  name                 = "example"
  # Set the secrets from AWS Secrets Manager
  username = local.your_secret.username
  password = local.your_secret.password
}
like image 30
Asri Badlah Avatar answered Nov 15 '22 06:11

Asri Badlah


variable "RdsAdminCred" {
  default = {
    username = "dbadmin"
    password = "dbadmin#02avia"
  }
  type = map(string)
}

resource "aws_secretsmanager_secret" "RdsAdminCred" {
  name = "RdsAdminCred"
}
resource "aws_secretsmanager_secret_version" "RdsAdminCred" {
  secret_id     = aws_secretsmanager_secret.RdsAdminCred.id
  secret_string = jsonencode(var.RdsAdminCred)
}

after you have created a secret, you need to take data from there

data "aws_secretsmanager_secret" "env_secrets" {
  name = "RdsAdminCred"
  depends_on = [
    aws_secretsmanager_secret.RdsAdminCred
  ]
}
data "aws_secretsmanager_secret_version" "current_secrets" {
  secret_id = data.aws_secretsmanager_secret.env_secrets.id
}
resource "aws_db_instance" "default" {
  identifier        = "testdb"
  allocated_storage = 20
  storage_type      = "gp2"
  engine            = "mysql"
  engine_version    = "5.7"
  instance_class    = "db.t2.medium"
  name              = "mydb"

  username = jsondecode(data.aws_secretsmanager_secret_version.current_secrets.secret_string)["username"]
  password = jsondecode(data.aws_secretsmanager_secret_version.current_secrets.secret_string)["password"]
}

like image 35
Magikon Avatar answered Nov 15 '22 06:11

Magikon