From ba9e40372adb820d175eabb9e439508fbed6f30c Mon Sep 17 00:00:00 2001 From: Vadim Kadnikov Date: Thu, 6 Jan 2022 18:37:00 +0100 Subject: [PATCH 1/3] feat: Create a KMS key for shareable secrets --- kms.tf | 43 +++++++++++++++++++++++++++++++++++++++++++ main.tf | 2 ++ 2 files changed, 45 insertions(+) create mode 100644 kms.tf diff --git a/kms.tf b/kms.tf new file mode 100644 index 0000000..f446e05 --- /dev/null +++ b/kms.tf @@ -0,0 +1,43 @@ +data "aws_iam_policy_document" "master" { + count = length(local.arns) > 0 ? 1 : 0 + + statement { + principals { + type = "AWS" + identifiers = [data.aws_caller_identity.current.account_id] + } + + actions = ["kms:*"] + resources = ["*"] + } + + statement { + principals { + type = "AWS" + identifiers = flatten(values(local.arns)) + } + + actions = [ + "kms:Decrypt", + "kms:DescribeKey", + ] + resources = ["*"] + } +} + +resource "aws_kms_key" "master" { + count = length(local.arns) > 0 ? 1 : 0 + + description = "The master key for ${var.app_name} application" + policy = data.aws_iam_policy_document.master[0].json + + tags = var.tags +} + +resource "aws_kms_alias" "master" { + count = length(local.arns) > 0 ? 1 : 0 + + name = "alias/${var.app_name}" + + target_key_id = aws_kms_key.master[0].key_id +} diff --git a/main.tf b/main.tf index 6a11838..350de64 100644 --- a/main.tf +++ b/main.tf @@ -20,6 +20,8 @@ resource "aws_secretsmanager_secret" "app" { name_prefix = "${var.app_name}-${each.key}" description = "The ${title(replace(each.key, "-", " "))} secret for ${var.app_name} application" + kms_key_id = length(local.arns) > 0 ? aws_kms_key.master[0].key_id : null + policy = lookup(local.arns, each.key, null) == null ? null : data.aws_iam_policy_document.access[each.key].json tags = merge(var.tags, { "service" = var.app_name }) From 1da0850c71ed0fcbc20091598047f1a5ff0a2e3a Mon Sep 17 00:00:00 2001 From: Vadim Kadnikov Date: Thu, 6 Jan 2022 18:37:19 +0100 Subject: [PATCH 2/3] feat: Update outputs --- outputs.tf | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/outputs.tf b/outputs.tf index 042d999..4f9e8cf 100644 --- a/outputs.tf +++ b/outputs.tf @@ -7,3 +7,13 @@ output "all" { } ] } + +output "kms_key_arn" { + description = "The master key ARN" + value = length(local.arns) > 0 ? aws_kms_key.master[0].arn : null +} + +output "kms_alias_arn" { + description = "The master key alias ARN" + value = length(local.arns) > 0 ? aws_kms_alias.master[0].arn : null +} From c06c540be933c0c3251b516f946211f1c2e005c9 Mon Sep 17 00:00:00 2001 From: Vadim Kadnikov Date: Thu, 6 Jan 2022 18:37:43 +0100 Subject: [PATCH 3/3] chore: Update README --- README.md | 172 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 153 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 9ca3711..1b865bf 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ A module to create application secrets stored in [AWS Secrets Manager](https://a * [Table of contents](#table-of-contents) * [Prerequisites](#prerequisites) * [Example usage](#example-usage) + * [Single-account secrets](#single-account-secrets) + * [Cross-account secrets](#cross-account-secrets) * [Inputs](#inputs) * [Secrets](#secrets) * [Outputs](#outputs) @@ -20,57 +22,187 @@ A module to create application secrets stored in [AWS Secrets Manager](https://a ## Example usage +### Single-account secrets + +This is a main use-case of the module. When you want to create application secrets that are not intended to be shared with other AWS accounts please refer to the following example: + ```hcl module "secrets" { source = "git::ssh://git@github.com/scribd/terraform-aws-app-secrets.git?ref=main" - app_name = "go-chassis" + app_name = "project" secrets = [ { name = "app-env" value = "development" allowed_arns = [] }, - { - name = "app-settings-name" - value = "go-chassis" - allowed_arns = [] - }, { name = "app-database-host" value = "[value required]" - allowed_arn = ["arn:aws:iam::1234567890:role/theirRole"] + allowed_arn = [] }, { name = "app-database-port" value = "3306" allowed_arns = [] - }, + } + ] + + tags = { + department = "engineering" + project = "project" + env = "development" + } +} +``` + +### Cross-account secrets + +The module allows you to [delegate](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_compare-resource-policies.html#aboutdelegation-resourcepolicy) read-only access to your secrets to other AWS accounts. Unfortunately, the configuration can't be fully provisioned by the module. It requires additional configuration in the AWS accounts where the secrets are requested from. Below you can find an example of sharing secrets with 2 different AWS accounts. + +1. Create secrets within an AWS account (in the example, we refer to it as `account_id1`) and specify AWS account ids or user ARNs that should have access to the secrets. The module generates [resource-based policies](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html#policies_resource-based) which are attached to a secret (one policy per secret): + +```hcl +module "secrets" { + source = "git::ssh://git@github.com/scribd/terraform-aws-app-secrets.git?ref=main" + + app_name = "project" + secrets = [ { - name = "app-database-username" - value = "[value required]" + name = "app-env" + value = "development" allowed_arns = [] }, { - name = "app-database-password" + name = "app-database-host" value = "[value required]" - allowed_arns = [] + allowed_arns = [var.account_id2] }, { - name = "app-database-name" - value = "[value required]" - allowed_arns = [] + name = "app-database-port" + value = "3306" + allowed_arns = [ + var.account_id2, + "arn:aws:iam::${var.account_id3}:user/user-name", + ] } ] tags = { department = "engineering" - project = "go-chassis" + project = "project" env = "development" } } ``` +The example above creates the secrets and grants access to the `app-database-host` secret to the `account_id2` AWS account. Access to the `app-database-port` secret is granted to the `account_id2` account and the `user-name` user defined in the `account_id3` AWS account. The `app-env` secret is not shared with any other AWS accounts. + +2. Run the terraform pipeline to provision the secrets and copy the KMS key ARN from the `module.secrets.kms_key_arn` output. + +3. In the `account_id2` AWS account, create the role `roleName` and attach a policy to it: + +```hcl +data "aws_iam_policy_document" "secret_access" { + statement { + actions = [ + "kms:Decrypt", + "kms:DescribeKey", + ] + + resources = ["kms-key-arn-copied-from-step-2"] + } + + statement { + actions = ["secretsmanager:GetSecretValue"] + + resources = [ + "arn:aws:secretsmanager:${var.aws_region}:${var.account_id1}:secret:project-app-database-host*", + "arn:aws:secretsmanager:${var.aws_region}:${var.account_id1}:secret:project-app-database-port*", + ] + } +} + +resource "aws_iam_policy" "secret_access" { + name_prefix = "project" + description = "Policy to access secrets for application project" + policy = data.aws_iam_policy_document.secret_access.json +} + +module "iam_assumable_role" { + source = "terraform-aws-modules/iam/aws//modules/iam-assumable-role" + version = "~> 4.8" + + create_role = true + + role_name = "roleName" + role_requires_mfa = false + + custom_role_policy_arns = [aws_iam_policy.secret_access.arn] + number_of_custom_role_policy_arns = 1 + + tags = { + department = "engineering" + project = "project" + env = "development" + } +} +``` + +Now you should be able to assume the role from within `account_id2` and read the secret value. + +> :warning: Note: As an example, we use a third-party module `iam-assumable-role` to create a new role. In your case, you may want to attach the newly created policy to an existing role. + +4. In the `account_id3` AWS account, create the user `user-name` and attach a policy to it: + +```hcl +data "aws_iam_policy_document" "secret_access" { + statement { + actions = [ + "kms:Decrypt", + "kms:DescribeKey", + ] + + resources = ["kms-key-arn-copied-from-step-2"] + } + + statement { + actions = ["secretsmanager:GetSecretValue"] + resources = ["arn:aws:secretsmanager:${var.aws_region}:${var.account_id1}:secret:project-app-database-port*"] + } +} + +resource "aws_iam_policy" "secret_access" { + name_prefix = "project" + description = "Policy to access secrets for application project" + policy = data.aws_iam_policy_document.secret_access.json +} + +resource "aws_iam_user_policy_attachment" "user" { + user = module.user.iam_user_name + policy_arn = aws_iam_policy.secret_access.arn +} + +module "user" { + source = "terraform-aws-modules/iam/aws//modules/iam-user" + version = "~> 4.8" + + name = "user-name" + + create_user = true + create_iam_user_login_profile = false + + tags = { + department = "engineering" + project = "project" + env = "development" + } +} +``` + +> ⚠️ Note: As an example, we use a third-party module `iam-user` to create a new user. In your case, you may want to attach the newly created policy to an existing user. + > ⚠️ **IMPORTANT NOTES** > > * Please don't use `ref=main` in your production code. Please refer to a release tag explicitly. @@ -95,9 +227,11 @@ module "secrets" { ## Outputs -| Name | Description | Sensitive | -| ---- | ---------------------------------------- | --------- | -| all | Map of names and arns of created secrets | no | +| Name | Description | Sensitive | +| --------------- | ---------------------------------------- | --------- | +| `all` | Map of names and arns of created secrets | no | +| `kms_key_arn` | The master key ARN | no | +| `kms_alias_arn` | The master key alias ARN | no | ## Release