Skip to content

Commit 1a3bfc3

Browse files
committed
feat: Implement user inline policy for iam-user
1 parent 03d1b49 commit 1a3bfc3

File tree

7 files changed

+287
-35
lines changed

7 files changed

+287
-35
lines changed

examples/iam-user/README.md

Lines changed: 6 additions & 2 deletions
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
@@ -35,6 +35,7 @@ No providers.
3535
| <a name="module_iam_user2"></a> [iam\_user2](#module\_iam\_user2) | ../../modules/iam-user | n/a |
3636
| <a name="module_iam_user3"></a> [iam\_user3](#module\_iam\_user3) | ../../modules/iam-user | n/a |
3737
| <a name="module_iam_user_disabled"></a> [iam\_user\_disabled](#module\_iam\_user\_disabled) | ../../modules/iam-user | n/a |
38+
| <a name="module_iam_user_with_inline_policy"></a> [iam\_user\_with\_inline\_policy](#module\_iam\_user\_with\_inline\_policy) | ../../modules/iam-user | n/a |
3839

3940
## Resources
4041

@@ -93,4 +94,7 @@ No inputs.
9394
| <a name="output_iam_user_ssh_key_fingerprint"></a> [iam\_user\_ssh\_key\_fingerprint](#output\_iam\_user\_ssh\_key\_fingerprint) | The MD5 message digest of the SSH public key |
9495
| <a name="output_iam_user_ssh_key_public_key_id"></a> [iam\_user\_ssh\_key\_public\_key\_id](#output\_iam\_user\_ssh\_key\_public\_key\_id) | The unique identifier for the SSH public key |
9596
| <a name="output_iam_user_unique_id"></a> [iam\_user\_unique\_id](#output\_iam\_user\_unique\_id) | The unique ID assigned by AWS |
96-
<!-- END_TF_DOCS -->
97+
| <a name="output_iam_user_with_inline_policy_arn"></a> [iam\_user\_with\_inline\_policy\_arn](#output\_iam\_user\_with\_inline\_policy\_arn) | The ARN assigned by AWS for this user |
98+
| <a name="output_iam_user_with_inline_policy_name"></a> [iam\_user\_with\_inline\_policy\_name](#output\_iam\_user\_with\_inline\_policy\_name) | The user's name |
99+
| <a name="output_iam_user_with_inline_policy_unique_id"></a> [iam\_user\_with\_inline\_policy\_unique\_id](#output\_iam\_user\_with\_inline\_policy\_unique\_id) | The unique ID assigned by AWS |
100+
<!-- END_TF_DOCS -->

examples/iam-user/main.tf

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,43 @@ module "iam_user_disabled" {
7171

7272
tags = local.tags
7373
}
74+
75+
################################################################################
76+
# IAM user with inline policy
77+
################################################################################
78+
79+
module "iam_user_with_inline_policy" {
80+
source = "../../modules/iam-user"
81+
82+
name = "vasya.pupkin6"
83+
force_destroy = true
84+
85+
# Enable inline policy
86+
create_inline_policy = true
87+
88+
# Define inline policy permissions
89+
inline_policy_permissions = {
90+
s3_read_access = {
91+
effect = "Allow"
92+
actions = [
93+
"s3:GetObject",
94+
"s3:ListBucket"
95+
]
96+
resources = [
97+
"arn:aws:s3:::example-bucket",
98+
"arn:aws:s3:::example-bucket/*"
99+
]
100+
}
101+
cloudwatch_logs = {
102+
effect = "Allow"
103+
actions = [
104+
"logs:CreateLogGroup",
105+
"logs:CreateLogStream",
106+
"logs:PutLogEvents"
107+
]
108+
resources = ["*"]
109+
}
110+
}
111+
112+
tags = local.tags
113+
}

modules/iam-user/README.md

Lines changed: 46 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

@@ -21,6 +21,45 @@ module "iam_user" {
2121
}
2222
```
2323

24+
### Inline Policies
25+
26+
You can also create inline policies for the IAM user:
27+
28+
```hcl
29+
module "iam_user_with_inline_policy" {
30+
source = "terraform-aws-modules/iam/aws//modules/iam-user"
31+
32+
name = "example-user"
33+
34+
# Enable inline policy
35+
create_inline_policy = true
36+
37+
# Define inline policy permissions
38+
inline_policy_permissions = {
39+
s3_access = {
40+
effect = "Allow"
41+
actions = [
42+
"s3:GetObject",
43+
"s3:ListBucket"
44+
]
45+
resources = [
46+
"arn:aws:s3:::example-bucket",
47+
"arn:aws:s3:::example-bucket/*"
48+
]
49+
}
50+
cloudwatch_logs = {
51+
effect = "Allow"
52+
actions = [
53+
"logs:CreateLogGroup",
54+
"logs:CreateLogStream",
55+
"logs:PutLogEvents"
56+
]
57+
resources = ["*"]
58+
}
59+
}
60+
}
61+
```
62+
2463
### Keybase
2564

2665
If possible, always use PGP encryption to prevent Terraform from keeping unencrypted password and access secret key in state file.
@@ -52,8 +91,10 @@ No modules.
5291
| [aws_iam_access_key.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_access_key) | resource |
5392
| [aws_iam_user.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user) | resource |
5493
| [aws_iam_user_login_profile.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user_login_profile) | resource |
94+
| [aws_iam_user_policy.inline](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user_policy) | resource |
5595
| [aws_iam_user_policy_attachment.additional](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user_policy_attachment) | resource |
5696
| [aws_iam_user_ssh_key.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user_ssh_key) | resource |
97+
| [aws_iam_policy_document.inline](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
5798

5899
## Inputs
59100

@@ -62,16 +103,20 @@ No modules.
62103
| <a name="input_access_key_status"></a> [access\_key\_status](#input\_access\_key\_status) | Access key status to apply | `string` | `null` | no |
63104
| <a name="input_create"></a> [create](#input\_create) | Controls if resources should be created (affects all resources) | `bool` | `true` | no |
64105
| <a name="input_create_access_key"></a> [create\_access\_key](#input\_create\_access\_key) | Whether to create IAM access key | `bool` | `true` | no |
106+
| <a name="input_create_inline_policy"></a> [create\_inline\_policy](#input\_create\_inline\_policy) | Determines whether to create an inline policy | `bool` | `false` | no |
65107
| <a name="input_create_login_profile"></a> [create\_login\_profile](#input\_create\_login\_profile) | Whether to create IAM user login profile | `bool` | `true` | no |
66108
| <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 |
67109
| <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 |
110+
| <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 |
68111
| <a name="input_name"></a> [name](#input\_name) | Desired name for the IAM user | `string` | `""` | no |
112+
| <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 |
69113
| <a name="input_password_length"></a> [password\_length](#input\_password\_length) | The length of the generated password | `number` | `null` | no |
70114
| <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 |
71115
| <a name="input_path"></a> [path](#input\_path) | Desired path for the IAM user | `string` | `null` | no |
72116
| <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 |
73117
| <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 |
74118
| <a name="input_policies"></a> [policies](#input\_policies) | Policies to attach to the IAM user in `{'static_name' = 'policy_arn'}` format | `map(string)` | `{}` | no |
119+
| <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 |
75120
| <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 |
76121
| <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 |
77122
| <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: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
################################################################################
2+
# Locals
3+
################################################################################
4+
5+
locals {
6+
create_iam_user_inline_policy = var.create && var.create_inline_policy
7+
}
8+
19
################################################################################
210
# User
311
################################################################################
@@ -20,6 +28,66 @@ resource "aws_iam_user_policy_attachment" "additional" {
2028
policy_arn = each.value
2129
}
2230

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

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+
# IAM User 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/README.md

Lines changed: 59 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -64,37 +64,80 @@ module "wrapper" {
6464
}
6565
```
6666

67-
## Example: Manage multiple S3 buckets in one Terragrunt layer
67+
## Example: Manage multiple IAM users in one Terragrunt layer
6868

69-
`eu-west-1/s3-buckets/terragrunt.hcl`:
69+
`eu-west-1/iam-users/terragrunt.hcl`:
7070

7171
```hcl
7272
terraform {
73-
source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers"
73+
source = "tfr:///terraform-aws-modules/iam/aws//wrappers/iam-user"
7474
# Alternative source:
75-
# source = "git::[email protected]:terraform-aws-modules/terraform-aws-s3-bucket.git//wrappers?ref=master"
75+
# source = "git::[email protected]:terraform-aws-modules/terraform-aws-iam.git//wrappers/iam-user?ref=master"
7676
}
7777
7878
inputs = {
7979
defaults = {
8080
force_destroy = true
81-
82-
attach_elb_log_delivery_policy = true
83-
attach_lb_log_delivery_policy = true
84-
attach_deny_insecure_transport_policy = true
85-
attach_require_latest_tls_policy = true
81+
pgp_key = "keybase:test"
82+
tags = {
83+
Terraform = "true"
84+
Environment = "dev"
85+
}
8686
}
8787
8888
items = {
89-
bucket1 = {
90-
bucket = "my-random-bucket-1"
91-
}
92-
bucket2 = {
93-
bucket = "my-random-bucket-2"
94-
tags = {
95-
Secure = "probably"
89+
user1 = {
90+
name = "john.doe"
91+
create_inline_policy = true
92+
inline_policy_permissions = {
93+
s3_access = {
94+
effect = "Allow"
95+
actions = ["s3:GetObject", "s3:ListBucket"]
96+
resources = ["arn:aws:s3:::example-bucket", "arn:aws:s3:::example-bucket/*"]
97+
}
9698
}
9799
}
100+
user2 = {
101+
name = "jane.smith"
102+
create_login_profile = false
103+
create_access_key = true
104+
}
98105
}
99106
}
100107
```
108+
109+
<!-- BEGIN_TF_DOCS -->
110+
## Requirements
111+
112+
| Name | Version |
113+
|------|---------|
114+
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.5.7 |
115+
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 6.0 |
116+
117+
## Providers
118+
119+
No providers.
120+
121+
## Modules
122+
123+
| Name | Source | Version |
124+
|------|--------|---------|
125+
| <a name="module_wrapper"></a> [wrapper](#module\_wrapper) | ../../modules/iam-user | n/a |
126+
127+
## Resources
128+
129+
No resources.
130+
131+
## Inputs
132+
133+
| Name | Description | Type | Default | Required |
134+
|------|-------------|------|---------|:--------:|
135+
| <a name="input_defaults"></a> [defaults](#input\_defaults) | Map of default values which will be used for each item. | `any` | `{}` | no |
136+
| <a name="input_items"></a> [items](#input\_items) | Maps of items to create a wrapper from. Values are passed through to the module. | `any` | `{}` | no |
137+
138+
## Outputs
139+
140+
| Name | Description |
141+
|------|-------------|
142+
| <a name="output_wrapper"></a> [wrapper](#output\_wrapper) | Map of outputs of a wrapper. |
143+
<!-- END_TF_DOCS -->

0 commit comments

Comments
 (0)