Skip to content

Commit 9478b48

Browse files
authored
Merge pull request #11 from scribd/vadimka/support-custom-kms-keys
[SERF-1384] Add support for cross-account access to secrets
2 parents 0cc3897 + c06c540 commit 9478b48

File tree

4 files changed

+208
-19
lines changed

4 files changed

+208
-19
lines changed

README.md

Lines changed: 153 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ A module to create application secrets stored in [AWS Secrets Manager](https://a
77
* [Table of contents](#table-of-contents)
88
* [Prerequisites](#prerequisites)
99
* [Example usage](#example-usage)
10+
* [Single-account secrets](#single-account-secrets)
11+
* [Cross-account secrets](#cross-account-secrets)
1012
* [Inputs](#inputs)
1113
* [Secrets](#secrets)
1214
* [Outputs](#outputs)
@@ -20,57 +22,187 @@ A module to create application secrets stored in [AWS Secrets Manager](https://a
2022

2123
## Example usage
2224

25+
### Single-account secrets
26+
27+
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:
28+
2329
```hcl
2430
module "secrets" {
2531
source = "git::ssh://git@github.com/scribd/terraform-aws-app-secrets.git?ref=main"
2632
27-
app_name = "go-chassis"
33+
app_name = "project"
2834
secrets = [
2935
{
3036
name = "app-env"
3137
value = "development"
3238
allowed_arns = []
3339
},
34-
{
35-
name = "app-settings-name"
36-
value = "go-chassis"
37-
allowed_arns = []
38-
},
3940
{
4041
name = "app-database-host"
4142
value = "[value required]"
42-
allowed_arn = ["arn:aws:iam::1234567890:role/theirRole"]
43+
allowed_arn = []
4344
},
4445
{
4546
name = "app-database-port"
4647
value = "3306"
4748
allowed_arns = []
48-
},
49+
}
50+
]
51+
52+
tags = {
53+
department = "engineering"
54+
project = "project"
55+
env = "development"
56+
}
57+
}
58+
```
59+
60+
### Cross-account secrets
61+
62+
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.
63+
64+
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):
65+
66+
```hcl
67+
module "secrets" {
68+
source = "git::ssh://git@github.com/scribd/terraform-aws-app-secrets.git?ref=main"
69+
70+
app_name = "project"
71+
secrets = [
4972
{
50-
name = "app-database-username"
51-
value = "[value required]"
73+
name = "app-env"
74+
value = "development"
5275
allowed_arns = []
5376
},
5477
{
55-
name = "app-database-password"
78+
name = "app-database-host"
5679
value = "[value required]"
57-
allowed_arns = []
80+
allowed_arns = [var.account_id2]
5881
},
5982
{
60-
name = "app-database-name"
61-
value = "[value required]"
62-
allowed_arns = []
83+
name = "app-database-port"
84+
value = "3306"
85+
allowed_arns = [
86+
var.account_id2,
87+
"arn:aws:iam::${var.account_id3}:user/user-name",
88+
]
6389
}
6490
]
6591
6692
tags = {
6793
department = "engineering"
68-
project = "go-chassis"
94+
project = "project"
6995
env = "development"
7096
}
7197
}
7298
```
7399

100+
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.
101+
102+
2. Run the terraform pipeline to provision the secrets and copy the KMS key ARN from the `module.secrets.kms_key_arn` output.
103+
104+
3. In the `account_id2` AWS account, create the role `roleName` and attach a policy to it:
105+
106+
```hcl
107+
data "aws_iam_policy_document" "secret_access" {
108+
statement {
109+
actions = [
110+
"kms:Decrypt",
111+
"kms:DescribeKey",
112+
]
113+
114+
resources = ["kms-key-arn-copied-from-step-2"]
115+
}
116+
117+
statement {
118+
actions = ["secretsmanager:GetSecretValue"]
119+
120+
resources = [
121+
"arn:aws:secretsmanager:${var.aws_region}:${var.account_id1}:secret:project-app-database-host*",
122+
"arn:aws:secretsmanager:${var.aws_region}:${var.account_id1}:secret:project-app-database-port*",
123+
]
124+
}
125+
}
126+
127+
resource "aws_iam_policy" "secret_access" {
128+
name_prefix = "project"
129+
description = "Policy to access secrets for application project"
130+
policy = data.aws_iam_policy_document.secret_access.json
131+
}
132+
133+
module "iam_assumable_role" {
134+
source = "terraform-aws-modules/iam/aws//modules/iam-assumable-role"
135+
version = "~> 4.8"
136+
137+
create_role = true
138+
139+
role_name = "roleName"
140+
role_requires_mfa = false
141+
142+
custom_role_policy_arns = [aws_iam_policy.secret_access.arn]
143+
number_of_custom_role_policy_arns = 1
144+
145+
tags = {
146+
department = "engineering"
147+
project = "project"
148+
env = "development"
149+
}
150+
}
151+
```
152+
153+
Now you should be able to assume the role from within `account_id2` and read the secret value.
154+
155+
> :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.
156+
157+
4. In the `account_id3` AWS account, create the user `user-name` and attach a policy to it:
158+
159+
```hcl
160+
data "aws_iam_policy_document" "secret_access" {
161+
statement {
162+
actions = [
163+
"kms:Decrypt",
164+
"kms:DescribeKey",
165+
]
166+
167+
resources = ["kms-key-arn-copied-from-step-2"]
168+
}
169+
170+
statement {
171+
actions = ["secretsmanager:GetSecretValue"]
172+
resources = ["arn:aws:secretsmanager:${var.aws_region}:${var.account_id1}:secret:project-app-database-port*"]
173+
}
174+
}
175+
176+
resource "aws_iam_policy" "secret_access" {
177+
name_prefix = "project"
178+
description = "Policy to access secrets for application project"
179+
policy = data.aws_iam_policy_document.secret_access.json
180+
}
181+
182+
resource "aws_iam_user_policy_attachment" "user" {
183+
user = module.user.iam_user_name
184+
policy_arn = aws_iam_policy.secret_access.arn
185+
}
186+
187+
module "user" {
188+
source = "terraform-aws-modules/iam/aws//modules/iam-user"
189+
version = "~> 4.8"
190+
191+
name = "user-name"
192+
193+
create_user = true
194+
create_iam_user_login_profile = false
195+
196+
tags = {
197+
department = "engineering"
198+
project = "project"
199+
env = "development"
200+
}
201+
}
202+
```
203+
204+
> ⚠️ 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.
205+
74206
> ⚠️ **IMPORTANT NOTES**
75207
>
76208
> * Please don't use `ref=main` in your production code. Please refer to a release tag explicitly.
@@ -95,9 +227,11 @@ module "secrets" {
95227

96228
## Outputs
97229

98-
| Name | Description | Sensitive |
99-
| ---- | ---------------------------------------- | --------- |
100-
| all | Map of names and arns of created secrets | no |
230+
| Name | Description | Sensitive |
231+
| --------------- | ---------------------------------------- | --------- |
232+
| `all` | Map of names and arns of created secrets | no |
233+
| `kms_key_arn` | The master key ARN | no |
234+
| `kms_alias_arn` | The master key alias ARN | no |
101235

102236
## Release
103237

kms.tf

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
data "aws_iam_policy_document" "master" {
2+
count = length(local.arns) > 0 ? 1 : 0
3+
4+
statement {
5+
principals {
6+
type = "AWS"
7+
identifiers = [data.aws_caller_identity.current.account_id]
8+
}
9+
10+
actions = ["kms:*"]
11+
resources = ["*"]
12+
}
13+
14+
statement {
15+
principals {
16+
type = "AWS"
17+
identifiers = flatten(values(local.arns))
18+
}
19+
20+
actions = [
21+
"kms:Decrypt",
22+
"kms:DescribeKey",
23+
]
24+
resources = ["*"]
25+
}
26+
}
27+
28+
resource "aws_kms_key" "master" {
29+
count = length(local.arns) > 0 ? 1 : 0
30+
31+
description = "The master key for ${var.app_name} application"
32+
policy = data.aws_iam_policy_document.master[0].json
33+
34+
tags = var.tags
35+
}
36+
37+
resource "aws_kms_alias" "master" {
38+
count = length(local.arns) > 0 ? 1 : 0
39+
40+
name = "alias/${var.app_name}"
41+
42+
target_key_id = aws_kms_key.master[0].key_id
43+
}

main.tf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ resource "aws_secretsmanager_secret" "app" {
2020
name_prefix = "${var.app_name}-${each.key}"
2121
description = "The ${title(replace(each.key, "-", " "))} secret for ${var.app_name} application"
2222

23+
kms_key_id = length(local.arns) > 0 ? aws_kms_key.master[0].key_id : null
24+
2325
policy = lookup(local.arns, each.key, null) == null ? null : data.aws_iam_policy_document.access[each.key].json
2426

2527
tags = merge(var.tags, { "service" = var.app_name })

outputs.tf

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,13 @@ output "all" {
77
}
88
]
99
}
10+
11+
output "kms_key_arn" {
12+
description = "The master key ARN"
13+
value = length(local.arns) > 0 ? aws_kms_key.master[0].arn : null
14+
}
15+
16+
output "kms_alias_arn" {
17+
description = "The master key alias ARN"
18+
value = length(local.arns) > 0 ? aws_kms_alias.master[0].arn : null
19+
}

0 commit comments

Comments
 (0)