Skip to content

Commit 1c2dfd7

Browse files
feat: Implement user inline policy for iam-user (#607)
Co-authored-by: Bryant Biggs <[email protected]>
1 parent 03d1b49 commit 1c2dfd7

File tree

9 files changed

+171
-25
lines changed

9 files changed

+171
-25
lines changed

examples/iam-user/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# AWS IAM User Example
22

3-
Configuration in this directory creates an IAM user with a random password, a pair of IAM access/secret keys and uploads IAM SSH public key.
3+
Configuration in this directory creates an IAM user with a random password, a pair of IAM access/secret keys, uploads IAM SSH public key, and demonstrates inline policy creation.
44
User password and secret key is encrypted using public key of keybase.io user named `test`.
55

66
# Usage

examples/iam-user/main.tf

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,30 @@ module "iam_user2" {
4545
create_login_profile = false
4646
create_access_key = true
4747

48+
create_inline_policy = true
49+
inline_policy_permissions = {
50+
s3_read_access = {
51+
effect = "Allow"
52+
actions = [
53+
"s3:GetObject",
54+
"s3:ListBucket"
55+
]
56+
resources = [
57+
"arn:aws:s3:::example-bucket",
58+
"arn:aws:s3:::example-bucket/*"
59+
]
60+
}
61+
cloudwatch_logs = {
62+
effect = "Allow"
63+
actions = [
64+
"logs:CreateLogGroup",
65+
"logs:CreateLogStream",
66+
"logs:PutLogEvents"
67+
]
68+
resources = ["*"]
69+
}
70+
}
71+
4872
tags = local.tags
4973
}
5074

modules/iam-role-for-service-accounts/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
> Upgrade to use EKS Pod Identity instead of IRSA
55
> A similar module for EKS Pod Identity is available [here](https://github.com/terraform-aws-modules/terraform-aws-eks-pod-identity).
66
7-
> [!INFO]
7+
> [!IMPORTANT]
88
> The [karpenter](https://github.com/terraform-aws-modules/terraform-aws-eks/tree/master/modules/karpenter) sub-module contains the necessary AWS resources for running Karpenter, including the Karpenter controller IAM role & policy
99
1010
Creates an IAM role which can be assumed by AWS EKS `ServiceAccount`s with optional policies for commonly used controllers/custom resources within EKS. The optional policies supported include:

modules/iam-role-for-service-accounts/main.tf

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -207,11 +207,11 @@ resource "aws_iam_role_policy_attachment" "this" {
207207
################################################################################
208208

209209
locals {
210-
create_iam_role_inline_policy = var.create && var.create_inline_policy
210+
create_inline_policy = var.create && var.create_inline_policy
211211
}
212212

213213
data "aws_iam_policy_document" "inline" {
214-
count = local.create_iam_role_inline_policy ? 1 : 0
214+
count = local.create_inline_policy ? 1 : 0
215215

216216
source_policy_documents = var.source_inline_policy_documents
217217
override_policy_documents = var.override_inline_policy_documents
@@ -259,7 +259,7 @@ data "aws_iam_policy_document" "inline" {
259259
}
260260

261261
resource "aws_iam_role_policy" "inline" {
262-
count = local.create_iam_role_inline_policy ? 1 : 0
262+
count = local.create_inline_policy ? 1 : 0
263263

264264
role = aws_iam_role.this[0].name
265265
name = var.use_name_prefix ? null : var.name

modules/iam-role/main.tf

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -306,11 +306,11 @@ resource "aws_iam_role_policy_attachment" "this" {
306306
################################################################################
307307

308308
locals {
309-
create_iam_role_inline_policy = var.create && var.create_inline_policy
309+
create_inline_policy = var.create && var.create_inline_policy
310310
}
311311

312312
data "aws_iam_policy_document" "inline" {
313-
count = local.create_iam_role_inline_policy ? 1 : 0
313+
count = local.create_inline_policy ? 1 : 0
314314

315315
source_policy_documents = var.source_inline_policy_documents
316316
override_policy_documents = var.override_inline_policy_documents
@@ -358,7 +358,7 @@ data "aws_iam_policy_document" "inline" {
358358
}
359359

360360
resource "aws_iam_role_policy" "inline" {
361-
count = local.create_iam_role_inline_policy ? 1 : 0
361+
count = local.create_inline_policy ? 1 : 0
362362

363363
role = aws_iam_role.this[0].name
364364
name = var.use_name_prefix ? null : var.name

modules/iam-user/README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# AWS IAM User Terraform Module
22

3-
Creates an IAM user with ability to create a login profile, access key, and SSH key.
3+
Creates an IAM user with ability to create a login profile, access key, SSH key, and inline policies.
44

55
## Usage
66

@@ -52,8 +52,10 @@ No modules.
5252
| [aws_iam_access_key.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_access_key) | resource |
5353
| [aws_iam_user.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user) | resource |
5454
| [aws_iam_user_login_profile.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user_login_profile) | resource |
55+
| [aws_iam_user_policy.inline](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user_policy) | resource |
5556
| [aws_iam_user_policy_attachment.additional](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user_policy_attachment) | resource |
5657
| [aws_iam_user_ssh_key.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user_ssh_key) | resource |
58+
| [aws_iam_policy_document.inline](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
5759

5860
## Inputs
5961

@@ -62,16 +64,20 @@ No modules.
6264
| <a name="input_access_key_status"></a> [access\_key\_status](#input\_access\_key\_status) | Access key status to apply | `string` | `null` | no |
6365
| <a name="input_create"></a> [create](#input\_create) | Controls if resources should be created (affects all resources) | `bool` | `true` | no |
6466
| <a name="input_create_access_key"></a> [create\_access\_key](#input\_create\_access\_key) | Whether to create IAM access key | `bool` | `true` | no |
67+
| <a name="input_create_inline_policy"></a> [create\_inline\_policy](#input\_create\_inline\_policy) | Determines whether to create an inline policy | `bool` | `false` | no |
6568
| <a name="input_create_login_profile"></a> [create\_login\_profile](#input\_create\_login\_profile) | Whether to create IAM user login profile | `bool` | `true` | no |
6669
| <a name="input_create_ssh_key"></a> [create\_ssh\_key](#input\_create\_ssh\_key) | Whether to upload a public ssh key to the IAM user | `bool` | `false` | no |
6770
| <a name="input_force_destroy"></a> [force\_destroy](#input\_force\_destroy) | When destroying this user, destroy even if it has non-Terraform-managed IAM access keys, login profile or MFA devices. Without force\_destroy a user with non-Terraform-managed access keys and login profile will fail to be destroyed | `bool` | `false` | no |
71+
| <a name="input_inline_policy_permissions"></a> [inline\_policy\_permissions](#input\_inline\_policy\_permissions) | A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for inline policy permissions | <pre>map(object({<br/> sid = optional(string)<br/> actions = optional(list(string))<br/> not_actions = optional(list(string))<br/> effect = optional(string, "Allow")<br/> resources = optional(list(string))<br/> not_resources = optional(list(string))<br/> principals = optional(list(object({<br/> type = string<br/> identifiers = list(string)<br/> })))<br/> not_principals = optional(list(object({<br/> type = string<br/> identifiers = list(string)<br/> })))<br/> condition = optional(list(object({<br/> test = string<br/> variable = string<br/> values = list(string)<br/> })))<br/> }))</pre> | `null` | no |
6872
| <a name="input_name"></a> [name](#input\_name) | Desired name for the IAM user | `string` | `""` | no |
73+
| <a name="input_override_inline_policy_documents"></a> [override\_inline\_policy\_documents](#input\_override\_inline\_policy\_documents) | List of IAM policy documents that are merged together into the exported document. In merging, statements with non-blank `sid`s will override statements with the same `sid` | `list(string)` | `[]` | no |
6974
| <a name="input_password_length"></a> [password\_length](#input\_password\_length) | The length of the generated password | `number` | `null` | no |
7075
| <a name="input_password_reset_required"></a> [password\_reset\_required](#input\_password\_reset\_required) | Whether the user should be forced to reset the generated password on first login | `bool` | `true` | no |
7176
| <a name="input_path"></a> [path](#input\_path) | Desired path for the IAM user | `string` | `null` | no |
7277
| <a name="input_permissions_boundary"></a> [permissions\_boundary](#input\_permissions\_boundary) | The ARN of the policy that is used to set the permissions boundary for the user | `string` | `null` | no |
7378
| <a name="input_pgp_key"></a> [pgp\_key](#input\_pgp\_key) | Either a base-64 encoded PGP public key, or a keybase username in the form `keybase:username`. Used to encrypt password and access key | `string` | `null` | no |
7479
| <a name="input_policies"></a> [policies](#input\_policies) | Policies to attach to the IAM user in `{'static_name' = 'policy_arn'}` format | `map(string)` | `{}` | no |
80+
| <a name="input_source_inline_policy_documents"></a> [source\_inline\_policy\_documents](#input\_source\_inline\_policy\_documents) | List of IAM policy documents that are merged together into the exported document. Statements must have unique `sid`s | `list(string)` | `[]` | no |
7581
| <a name="input_ssh_key_encoding"></a> [ssh\_key\_encoding](#input\_ssh\_key\_encoding) | Specifies the public key encoding format to use in the response. To retrieve the public key in ssh-rsa format, use SSH. To retrieve the public key in PEM format, use PEM | `string` | `"SSH"` | no |
7682
| <a name="input_ssh_public_key"></a> [ssh\_public\_key](#input\_ssh\_public\_key) | The SSH public key. The public key must be encoded in ssh-rsa format or PEM format | `string` | `""` | no |
7783
| <a name="input_tags"></a> [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no |

modules/iam-user/main.tf

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,70 @@ resource "aws_iam_user_policy_attachment" "additional" {
2020
policy_arn = each.value
2121
}
2222

23+
################################################################################
24+
# IAM User Inline policy
25+
################################################################################
26+
27+
locals {
28+
create_inline_policy = var.create && var.create_inline_policy
29+
}
30+
31+
data "aws_iam_policy_document" "inline" {
32+
count = local.create_inline_policy ? 1 : 0
33+
34+
source_policy_documents = var.source_inline_policy_documents
35+
override_policy_documents = var.override_inline_policy_documents
36+
37+
dynamic "statement" {
38+
for_each = var.inline_policy_permissions != null ? var.inline_policy_permissions : {}
39+
40+
content {
41+
sid = try(coalesce(statement.value.sid, statement.key))
42+
actions = statement.value.actions
43+
not_actions = statement.value.not_actions
44+
effect = statement.value.effect
45+
resources = statement.value.resources
46+
not_resources = statement.value.not_resources
47+
48+
dynamic "principals" {
49+
for_each = statement.value.principals != null ? statement.value.principals : []
50+
51+
content {
52+
type = principals.value.type
53+
identifiers = principals.value.identifiers
54+
}
55+
}
56+
57+
dynamic "not_principals" {
58+
for_each = statement.value.not_principals != null ? statement.value.not_principals : []
59+
60+
content {
61+
type = not_principals.value.type
62+
identifiers = not_principals.value.identifiers
63+
}
64+
}
65+
66+
dynamic "condition" {
67+
for_each = statement.value.condition != null ? statement.value.condition : []
68+
69+
content {
70+
test = condition.value.test
71+
values = condition.value.values
72+
variable = condition.value.variable
73+
}
74+
}
75+
}
76+
}
77+
}
78+
79+
resource "aws_iam_user_policy" "inline" {
80+
count = local.create_inline_policy ? 1 : 0
81+
82+
user = aws_iam_user.this[0].name
83+
name = var.name
84+
policy = data.aws_iam_policy_document.inline[0].json
85+
}
86+
2387
################################################################################
2488
# User Login Profile
2589
################################################################################

modules/iam-user/variables.tf

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,51 @@ variable "ssh_public_key" {
109109
type = string
110110
default = ""
111111
}
112+
113+
################################################################################
114+
# Inline policy
115+
################################################################################
116+
117+
variable "create_inline_policy" {
118+
description = "Determines whether to create an inline policy"
119+
type = bool
120+
default = false
121+
}
122+
123+
variable "source_inline_policy_documents" {
124+
description = "List of IAM policy documents that are merged together into the exported document. Statements must have unique `sid`s"
125+
type = list(string)
126+
default = []
127+
}
128+
129+
variable "override_inline_policy_documents" {
130+
description = "List of IAM policy documents that are merged together into the exported document. In merging, statements with non-blank `sid`s will override statements with the same `sid`"
131+
type = list(string)
132+
default = []
133+
}
134+
135+
variable "inline_policy_permissions" {
136+
description = "A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for inline policy permissions"
137+
type = map(object({
138+
sid = optional(string)
139+
actions = optional(list(string))
140+
not_actions = optional(list(string))
141+
effect = optional(string, "Allow")
142+
resources = optional(list(string))
143+
not_resources = optional(list(string))
144+
principals = optional(list(object({
145+
type = string
146+
identifiers = list(string)
147+
})))
148+
not_principals = optional(list(object({
149+
type = string
150+
identifiers = list(string)
151+
})))
152+
condition = optional(list(object({
153+
test = string
154+
variable = string
155+
values = list(string)
156+
})))
157+
}))
158+
default = null
159+
}

wrappers/iam-user/main.tf

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,24 @@ module "wrapper" {
33

44
for_each = var.items
55

6-
access_key_status = try(each.value.access_key_status, var.defaults.access_key_status, null)
7-
create = try(each.value.create, var.defaults.create, true)
8-
create_access_key = try(each.value.create_access_key, var.defaults.create_access_key, true)
9-
create_login_profile = try(each.value.create_login_profile, var.defaults.create_login_profile, true)
10-
create_ssh_key = try(each.value.create_ssh_key, var.defaults.create_ssh_key, false)
11-
force_destroy = try(each.value.force_destroy, var.defaults.force_destroy, false)
12-
name = try(each.value.name, var.defaults.name, "")
13-
password_length = try(each.value.password_length, var.defaults.password_length, null)
14-
password_reset_required = try(each.value.password_reset_required, var.defaults.password_reset_required, true)
15-
path = try(each.value.path, var.defaults.path, null)
16-
permissions_boundary = try(each.value.permissions_boundary, var.defaults.permissions_boundary, null)
17-
pgp_key = try(each.value.pgp_key, var.defaults.pgp_key, null)
18-
policies = try(each.value.policies, var.defaults.policies, {})
19-
ssh_key_encoding = try(each.value.ssh_key_encoding, var.defaults.ssh_key_encoding, "SSH")
20-
ssh_public_key = try(each.value.ssh_public_key, var.defaults.ssh_public_key, "")
21-
tags = try(each.value.tags, var.defaults.tags, {})
6+
access_key_status = try(each.value.access_key_status, var.defaults.access_key_status, null)
7+
create = try(each.value.create, var.defaults.create, true)
8+
create_access_key = try(each.value.create_access_key, var.defaults.create_access_key, true)
9+
create_inline_policy = try(each.value.create_inline_policy, var.defaults.create_inline_policy, false)
10+
create_login_profile = try(each.value.create_login_profile, var.defaults.create_login_profile, true)
11+
create_ssh_key = try(each.value.create_ssh_key, var.defaults.create_ssh_key, false)
12+
force_destroy = try(each.value.force_destroy, var.defaults.force_destroy, false)
13+
inline_policy_permissions = try(each.value.inline_policy_permissions, var.defaults.inline_policy_permissions, null)
14+
name = try(each.value.name, var.defaults.name, "")
15+
override_inline_policy_documents = try(each.value.override_inline_policy_documents, var.defaults.override_inline_policy_documents, [])
16+
password_length = try(each.value.password_length, var.defaults.password_length, null)
17+
password_reset_required = try(each.value.password_reset_required, var.defaults.password_reset_required, true)
18+
path = try(each.value.path, var.defaults.path, null)
19+
permissions_boundary = try(each.value.permissions_boundary, var.defaults.permissions_boundary, null)
20+
pgp_key = try(each.value.pgp_key, var.defaults.pgp_key, null)
21+
policies = try(each.value.policies, var.defaults.policies, {})
22+
source_inline_policy_documents = try(each.value.source_inline_policy_documents, var.defaults.source_inline_policy_documents, [])
23+
ssh_key_encoding = try(each.value.ssh_key_encoding, var.defaults.ssh_key_encoding, "SSH")
24+
ssh_public_key = try(each.value.ssh_public_key, var.defaults.ssh_public_key, "")
25+
tags = try(each.value.tags, var.defaults.tags, {})
2226
}

0 commit comments

Comments
 (0)