├── .gitignore ├── README.md ├── main.tf ├── parameterstore.tf ├── provider.tf ├── secretsmanager.tf ├── templates └── task.json.tpl └── variables.tf /.gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | 4 | # .tfstate files 5 | *.tfstate 6 | *.tfstate.* 7 | 8 | # Crash log files 9 | crash.log 10 | 11 | # Ignore any .tfvars files that are generated automatically for each Terraform run. Most 12 | # .tfvars files are managed as part of configuration and so should be included in 13 | # version control. 14 | # 15 | # example.tfvars 16 | 17 | # Ignore override files as they are usually used to override resources locally and so 18 | # are not checked in 19 | override.tf 20 | override.tf.json 21 | *_override.tf 22 | *_override.tf.json 23 | 24 | # Include override files you do wish to add to version control using negated pattern 25 | # 26 | # !example_override.tf 27 | 28 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 29 | # example: *tfplan* 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Terraform ECS Secrets 2 | 3 | Store secrets on Parameter Store vs AWS Secrets Manager and inject them into Amazon ECS tasks using Terraform. 4 | 5 | Check full blog post from: 6 | 7 | https://www.sufle.io/blog/keeping-secrets-as-secret-on-amazon-ecs-using-terraform 8 | -------------------------------------------------------------------------------- /main.tf: -------------------------------------------------------------------------------- 1 | # ECS Task Execution IAM Role 2 | data "aws_iam_policy_document" "ecs_task_execution_assume_role_policy" { 3 | statement { 4 | actions = ["sts:AssumeRole"] 5 | 6 | principals { 7 | type = "Service" 8 | identifiers = ["ecs-tasks.amazonaws.com"] 9 | } 10 | } 11 | } 12 | 13 | resource "aws_iam_role" "ecs_task_execution_role" { 14 | name = "ecs-task-execution" 15 | assume_role_policy = data.aws_iam_policy_document.ecs_task_execution_assume_role_policy.json 16 | path = "/" 17 | } 18 | 19 | data "aws_iam_policy" "ecs_task_execution" { 20 | arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" 21 | } 22 | 23 | resource "aws_iam_role_policy_attachment" "ecs_task_execution" { 24 | role = aws_iam_role.ecs_task_execution_role.name 25 | policy_arn = data.aws_iam_policy.ecs_task_execution.arn 26 | } 27 | 28 | # Random password 29 | resource "random_password" "database_password" { 30 | length = 16 31 | special = false 32 | } 33 | -------------------------------------------------------------------------------- /parameterstore.tf: -------------------------------------------------------------------------------- 1 | resource "aws_ssm_parameter" "database_password_parameter" { 2 | name = "/production/database/password/master" 3 | description = "Production environment database password" 4 | type = "SecureString" 5 | value = random_password.database_password.result 6 | } 7 | 8 | resource "aws_iam_role_policy" "password_policy_parameterstore" { 9 | name = "password-policy-parameterstore" 10 | role = aws_iam_role.ecs_task_execution_role.id 11 | 12 | policy = <<-EOF 13 | { 14 | "Version": "2012-10-17", 15 | "Statement": [ 16 | { 17 | "Action": [ 18 | "ssm:GetParameters" 19 | ], 20 | "Effect": "Allow", 21 | "Resource": [ 22 | "${aws_ssm_parameter.database_password_parameter.arn}" 23 | ] 24 | } 25 | ] 26 | } 27 | EOF 28 | } 29 | 30 | data "template_file" "task_template_parameterstore" { 31 | template = "${file("./templates/task.json.tpl")}" 32 | 33 | vars = { 34 | app_cpu = var.cpu 35 | app_memory = var.memory 36 | database_password = aws_ssm_parameter.database_password_parameter.arn 37 | } 38 | } 39 | 40 | resource "aws_ecs_task_definition" "task_definition_parameterstore" { 41 | family = "task-parameterstore" 42 | execution_role_arn = aws_iam_role.ecs_task_execution_role.arn 43 | requires_compatibilities = ["EC2"] 44 | cpu = var.cpu 45 | memory = var.memory 46 | network_mode = "awsvpc" 47 | container_definitions = data.template_file.task_template_parameterstore.rendered 48 | } 49 | -------------------------------------------------------------------------------- /provider.tf: -------------------------------------------------------------------------------- 1 | # AWS provider 2 | provider "aws" { 3 | region = "eu-west-1" 4 | version = "~> 2.68" 5 | } 6 | 7 | # Template provider 8 | provider "template" { 9 | version = "~> 2.1" 10 | } 11 | 12 | # Random generator provider 13 | provider "random" { 14 | version = "~> 2.3" 15 | } 16 | -------------------------------------------------------------------------------- /secretsmanager.tf: -------------------------------------------------------------------------------- 1 | resource "aws_secretsmanager_secret" "database_password_secret" { 2 | name = "/production/database/password/master" 3 | } 4 | 5 | resource "aws_secretsmanager_secret_version" "database_password_secret_version" { 6 | secret_id = aws_secretsmanager_secret.database_password_secret.id 7 | secret_string = random_password.database_password.result 8 | } 9 | 10 | resource "aws_iam_role_policy" "password_policy_secretsmanager" { 11 | name = "password-policy-secretsmanager" 12 | role = aws_iam_role.ecs_task_execution_role.id 13 | 14 | policy = <<-EOF 15 | { 16 | "Version": "2012-10-17", 17 | "Statement": [ 18 | { 19 | "Action": [ 20 | "secretsmanager:GetSecretValue" 21 | ], 22 | "Effect": "Allow", 23 | "Resource": [ 24 | "${aws_secretsmanager_secret.database_password_secret.arn}" 25 | ] 26 | } 27 | ] 28 | } 29 | EOF 30 | } 31 | 32 | data "template_file" "task_template_secretsmanager" { 33 | template = "${file("./templates/task.json.tpl")}" 34 | 35 | vars = { 36 | app_cpu = var.cpu 37 | app_memory = var.memory 38 | database_password = aws_secretsmanager_secret.database_password_secret.arn 39 | } 40 | } 41 | 42 | resource "aws_ecs_task_definition" "task_definition_secretsmanager" { 43 | family = "task-secretsmanager" 44 | execution_role_arn = aws_iam_role.ecs_task_execution_role.arn 45 | requires_compatibilities = ["EC2"] 46 | cpu = var.cpu 47 | memory = var.memory 48 | network_mode = "awsvpc" 49 | container_definitions = data.template_file.task_template_secretsmanager.rendered 50 | } 51 | -------------------------------------------------------------------------------- /templates/task.json.tpl: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "application", 4 | "image": "service/latest", 5 | "cpu": ${app_cpu}, 6 | "memory": ${app_memory}, 7 | "essential": true, 8 | "secrets": [ 9 | { 10 | "name": "PASSWORD", 11 | "valueFrom": "${database_password}" 12 | } 13 | ] 14 | } 15 | ] 16 | -------------------------------------------------------------------------------- /variables.tf: -------------------------------------------------------------------------------- 1 | variable "cpu" { 2 | default = "1024" 3 | description = "Task CPU" 4 | } 5 | 6 | variable "memory" { 7 | default = "1024" 8 | description = "Task Memory" 9 | } 10 | --------------------------------------------------------------------------------