├── examples
├── saml-login
│ ├── variables.tf
│ ├── data
│ │ ├── provider-saml.xml
│ │ ├── policy-sts-assume.json
│ │ └── trust-policy-saml.json
│ ├── outputs.tf
│ ├── main.tf
│ ├── terraform.tfvars
│ └── README.md
├── groups-users-and-policies
│ ├── variables.tf
│ ├── outputs.tf
│ ├── data
│ │ ├── rds-authenticate.json.tmpl
│ │ └── billing-ro.json
│ ├── main.tf
│ ├── terraform.tfvars
│ └── README.md
├── policies-with-custom-data-sources
│ ├── variables.tf
│ ├── outputs.tf
│ ├── data
│ │ └── trust-policy-file.json
│ ├── terraform.tfvars
│ ├── main.tf
│ └── README.md
├── policies
│ ├── outputs.tf
│ ├── data
│ │ ├── rds-authenticate.json.tmpl
│ │ └── billing-ro.json
│ ├── terraform.tfvars
│ ├── variables.tf
│ ├── main.tf
│ └── README.md
├── roles
│ ├── outputs.tf
│ ├── data
│ │ ├── trust-policy-file.json
│ │ ├── rds-authenticate.json.tmpl
│ │ └── billing-ro.json
│ ├── main.tf
│ ├── variables.tf
│ ├── terraform.tfvars
│ └── README.md
├── users
│ ├── outputs.tf
│ ├── data
│ │ ├── rds-authenticate.json.tmpl
│ │ └── billing-ro.json
│ ├── main.tf
│ ├── variables.tf
│ ├── terraform.tfvars
│ └── README.md
├── groups
│ ├── outputs.tf
│ ├── data
│ │ ├── rds-authenticate.json.tmpl
│ │ └── billing-ro.json
│ ├── main.tf
│ ├── variables.tf
│ ├── terraform.tfvars
│ └── README.md
└── access-key-rotation
│ └── README.md
├── .gitignore
├── LICENSE
├── .github
└── workflows
│ ├── test.yml
│ └── lint.yml
├── outputs-debug.tf
├── outputs.tf
├── Makefile
├── main.tf
├── locals.tf
├── variables.tf
└── README.md
/examples/saml-login/variables.tf:
--------------------------------------------------------------------------------
1 | ../../variables.tf
--------------------------------------------------------------------------------
/examples/groups-users-and-policies/variables.tf:
--------------------------------------------------------------------------------
1 | ../../variables.tf
--------------------------------------------------------------------------------
/examples/policies-with-custom-data-sources/variables.tf:
--------------------------------------------------------------------------------
1 | ../../variables.tf
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Deny Terraform runtime files
2 | .terraform
3 | terraform.tfstate
4 | *.tfstate*
5 | .terraform.lock.hcl
6 | /terraform.tfvars
7 |
--------------------------------------------------------------------------------
/examples/policies-with-custom-data-sources/outputs.tf:
--------------------------------------------------------------------------------
1 | output "roles" {
2 | description = "Created roles"
3 | value = module.aws_iam.roles
4 | }
5 |
--------------------------------------------------------------------------------
/examples/policies/outputs.tf:
--------------------------------------------------------------------------------
1 | output "policies" {
2 | description = "Created customer managed IAM policies"
3 | value = module.aws_iam.policies
4 | }
5 |
--------------------------------------------------------------------------------
/examples/saml-login/data/provider-saml.xml:
--------------------------------------------------------------------------------
1 |
list(object({
name = string # Name of the policy
path = string # Defaults to 'var.policy_path' if variable is set to null
desc = string # Defaults to 'var.policy_desc' if variable is set to null
file = string # Path to json or json.tmpl file of policy
vars = map(string) # Policy template variables {key: val, ...}
})) | `[]` | no |
88 | | roles | A list of dictionaries defining all roles. | list(object({
name = string # Name of the role
path = string # Defaults to 'var.role_path' if variable is set to null
desc = string # Defaults to 'var.role_desc' if variable is set to null
trust_policy_file = string # Path to file of trust/assume policy
permissions_boundary = string # ARN to a policy used as permissions boundary (or null/empty)
policies = list(string) # List of names of policies (must be defined in var.policies)
policy_arns = list(string) # List of existing policy ARN's
inline_policies = list(object({
name = string # Name of the inline policy
file = string # Path to json or json.tmpl file of policy
vars = map(string) # Policy template variables {key = val, ...}
}))
})) | `[]` | no |
89 |
90 | ## Outputs
91 |
92 | | Name | Description |
93 | |------|-------------|
94 | | policies | Created customer managed IAM policies |
95 | | roles | Created roles |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/examples/groups/README.md:
--------------------------------------------------------------------------------
1 | # Groups
2 |
3 | This example creates policies and various different groups.
4 |
5 |
6 | ## Overview
7 |
8 | You can define as many groups as desired and reference them by their names in **[`var.users`](../users/)** in order to attach as many groups to a specific user as needed.
9 | * When using the `policies` key, respective policies must be defined in **[`var.policies`](../policies/)**.
10 |
11 |
12 | ## Examples
13 |
14 | **Note:** The following example only shows the creation of a single group.
15 | You can however create as many groups as desired. Also re-arranging them within the list will not
16 | trigger terraform to change or destroy resources as they're internally stored in a map (rather than a list) by their group names as keys (See module's `locals.tf` for transformation).
17 |
18 | Groups are defined as follows:
19 |
20 | `terraform.tfvars`
21 | ```hcl
22 | groups = [
23 | {
24 | name = "group-name" # Name of the group (reference this in var.users for attachment)
25 | path = "/path/" # Defaults to 'var.group_path' if variable is set to null
26 | policies = [
27 | "policy-name-1", # policy-name-1 must be defined in var.policies
28 | "policy-name-2", # policy-name-2 must be defined in var.policies
29 | ]
30 | policy_arns = [ # Attach policies by ARN
31 | "arn:aws:iam::aws:policy/AmazonEC2FullAccess",
32 | "arn:aws:iam::aws:policy/AWSResourceAccessManagerFullAccess",
33 | ]
34 | inline_policies = [ # Attach inline policies defined via JSON files
35 | {
36 | name = "inline-policy-1"
37 | file = "data/policies/kms-ro.json"
38 | vars = {}
39 | },
40 | {
41 | name = "inline-policy-2"
42 | file = "data/policies/sqs-ro.json.tmpl"
43 | vars = { # You can use variables inside JSON files
44 | var1 = "Some value",
45 | var2 = "Another value",
46 | }
47 | },
48 | ]
49 | },
50 | ]
51 | ```
52 |
53 | If you want to attach dynamic policies created via [`aws_iam_policy_document`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document). Have a look at this **[Example](../policies-with-custom-data-sources)**.
54 |
55 |
56 | ## Usage
57 |
58 | To run this example you need to execute:
59 |
60 | ```bash
61 | $ terraform init
62 | $ terraform plan
63 | $ terraform apply
64 | ```
65 |
66 | Note that this example may create resources which cost money. Run terraform destroy when you don't need these resources.
67 |
68 |
69 |
70 | ## Requirements
71 |
72 | No requirements.
73 |
74 | ## Providers
75 |
76 | No provider.
77 |
78 | ## Inputs
79 |
80 | | Name | Description | Type | Default | Required |
81 | |------|-------------|------|---------|:--------:|
82 | | policies | A list of dictionaries defining all policies. | list(object({
name = string # Name of the policy
path = string # Defaults to 'var.policy_path' if variable is set to null
desc = string # Defaults to 'var.policy_desc' if variable is set to null
file = string # Path to json or json.tmpl file of policy
vars = map(string) # Policy template variables {key: val, ...}
})) | `[]` | no |
83 | | groups | A list of dictionaries defining all groups. | list(object({
name = string # Name of the group
path = string # Defaults to 'var.group_path' if variable is set to null
policies = list(string) # List of names of policies (must be defined in var.policies)
policy_arns = list(string) # List of existing policy ARN's
inline_policies = list(object({
name = string # Name of the inline policy
file = string # Path to json or json.tmpl file of policy
vars = map(string) # Policy template variables {key = val, ...}
}))
})) | `[]` | no |
84 |
85 | ## Outputs
86 |
87 | | Name | Description |
88 | |------|-------------|
89 | | policies | Created customer managed IAM policies |
90 | | groups | Created groups |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/outputs.tf:
--------------------------------------------------------------------------------
1 | # -------------------------------------------------------------------------------------------------
2 | # Account Settings
3 | # -------------------------------------------------------------------------------------------------
4 |
5 | output "account_alias" {
6 | description = "Created Account alias."
7 | value = aws_iam_account_alias.default
8 | }
9 |
10 | output "account_pass_policy" {
11 | description = "Created Account password policy."
12 | value = aws_iam_account_password_policy.default
13 | }
14 |
15 |
16 | # -------------------------------------------------------------------------------------------------
17 | # Identity Providers
18 | # -------------------------------------------------------------------------------------------------
19 |
20 | output "providers_saml" {
21 | description = "Created SAML providers."
22 | value = aws_iam_saml_provider.default
23 | }
24 |
25 | output "providers_oidc" {
26 | description = "Created OpenID Connect providers."
27 | value = aws_iam_openid_connect_provider.default
28 | }
29 |
30 |
31 | # -------------------------------------------------------------------------------------------------
32 | # Policies
33 | # -------------------------------------------------------------------------------------------------
34 |
35 | output "policies" {
36 | description = "Created customer managed IAM policies"
37 | value = aws_iam_policy.policies
38 | }
39 |
40 |
41 | # -------------------------------------------------------------------------------------------------
42 | # Groups
43 | # -------------------------------------------------------------------------------------------------
44 |
45 | output "groups" {
46 | description = "Created IAM groups"
47 | value = aws_iam_group.groups
48 | }
49 |
50 | output "group_policy_attachments" {
51 | description = "Attached group customer managed IAM policies"
52 | value = aws_iam_group_policy_attachment.policy_attachments
53 | }
54 |
55 | output "group_inline_policy_attachments" {
56 | description = "Attached group inline IAM policies"
57 | value = aws_iam_group_policy.inline_policy_attachments
58 | }
59 |
60 | output "group_policy_arn_attachments" {
61 | description = "Attached group IAM policy arns"
62 | value = aws_iam_group_policy_attachment.policy_arn_attachments
63 | }
64 |
65 |
66 | # -------------------------------------------------------------------------------------------------
67 | # Users
68 | # -------------------------------------------------------------------------------------------------
69 |
70 | output "users" {
71 | description = "Created IAM users"
72 | value = aws_iam_user.users
73 | }
74 |
75 | output "user_policy_attachments" {
76 | description = "Attached user customer managed IAM policies"
77 | value = aws_iam_user_policy_attachment.policy_attachments
78 | }
79 |
80 | output "user_inline_policy_attachments" {
81 | description = "Attached user inline IAM policies"
82 | value = aws_iam_user_policy.inline_policy_attachments
83 | }
84 |
85 | output "user_policy_arn_attachments" {
86 | description = "Attached user IAM policy arns"
87 | value = aws_iam_user_policy_attachment.policy_arn_attachments
88 | }
89 |
90 | output "user_group_memberships" {
91 | description = "Assigned user/group memberships"
92 | value = aws_iam_user_group_membership.group_membership
93 | }
94 |
95 |
96 | # -------------------------------------------------------------------------------------------------
97 | # Roles
98 | # -------------------------------------------------------------------------------------------------
99 |
100 | output "roles" {
101 | description = "Created IAM roles"
102 | value = aws_iam_role.roles
103 | }
104 |
105 | output "role_policy_attachments" {
106 | description = "Attached role customer managed IAM policies"
107 | value = aws_iam_role_policy_attachment.policy_attachments
108 | }
109 |
110 | output "role_inline_policy_attachments" {
111 | description = "Attached role inline IAM policies"
112 | value = aws_iam_role_policy.inline_policy_attachments
113 | }
114 |
115 | output "role_policy_arn_attachments" {
116 | description = "Attached role IAM policy arns"
117 | value = aws_iam_role_policy_attachment.policy_arn_attachments
118 | }
119 |
--------------------------------------------------------------------------------
/examples/users/README.md:
--------------------------------------------------------------------------------
1 | # Users
2 |
3 | This example creates policies and various different users.
4 |
5 |
6 | ## Overview
7 |
8 | You can define as many users as desired.
9 | * When using the `groups` key, respective groups must be defined in **[`var.groups`](../groups/)**.
10 | * When using the `policies` key, respective policies must be defined in **[`var.policies`](../policies/)**.
11 |
12 |
13 | ## Examples
14 |
15 | **Note:** The following examples only shows the creation of a single user. You can however create as many users as desired. Also re-arranging them within the list will not trigger terraform to change or destroy resources as they're internally stored in a map (rather than a list) by their user names as keys (See module's `locals.tf` for transformation).
16 |
17 | Users are defined as follows:
18 |
19 | `terraform.tfvars`
20 | ```hcl
21 | users = [
22 | {
23 | name = "username-1" # Name of the user
24 | path = "/path/" # Defaults to 'var.user_path' if variable is set to null
25 | groups = [
26 | "group-name-1", # group-name-1 must be defined in var.groups
27 | "group-name-2", # group-name-1 must be defined in var.groups
28 | ]
29 | access_keys = [ # You can create up to two access keys
30 | {
31 | name = "key-1"
32 | pgp_key = ""
33 | status = "Inactive"
34 | },
35 | {
36 | name = "key-2"
37 | pgp_key = ""
38 | status = "Active"
39 | },
40 | ]
41 | permissions_boundary = "arn:aws:iam::aws:policy/PowerUserAccess"
42 | policies = [
43 | "policy-name-1", # policy-name-1 must be defined in var.policies
44 | "policy-name-2", # policy-name-2 must be defined in var.policies
45 | ]
46 | policy_arns = [ # Attach policies by ARN
47 | "arn:aws:iam::aws:policy/AmazonEC2FullAccess",
48 | "arn:aws:iam::aws:policy/AWSResourceAccessManagerFullAccess",
49 | ]
50 | inline_policies = [ # Attach inline policies defined via JSON files
51 | {
52 | name = "inline-policy-1"
53 | file = "data/policies/kms-ro.json"
54 | vars = {}
55 | },
56 | {
57 | name = "inline-policy-2"
58 | file = "data/policies/sqs-ro.json.tmpl"
59 | vars = { # You can use variables inside JSON files
60 | var1 = "Some value",
61 | var2 = "Another value",
62 | }
63 | },
64 | ]
65 | },
66 | ]
67 | ```
68 |
69 | If you want to attach dyamic policies created via [`aws_iam_policy_document`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document). Have a look at this **[Example](../policies-with-custom-data-sources)**.
70 |
71 |
72 | ## Usage
73 |
74 | To run this example you need to execute:
75 |
76 | ```bash
77 | $ terraform init
78 | $ terraform plan
79 | $ terraform apply
80 | ```
81 |
82 | Note that this example may create resources which cost money. Run terraform destroy when you don't need these resources.
83 |
84 |
85 |
86 | ## Requirements
87 |
88 | No requirements.
89 |
90 | ## Providers
91 |
92 | No provider.
93 |
94 | ## Inputs
95 |
96 | | Name | Description | Type | Default | Required |
97 | |------|-------------|------|---------|:--------:|
98 | | policies | A list of dictionaries defining all policies. | list(object({
name = string # Name of the policy
path = string # Defaults to 'var.policy_path' if variable is set to null
desc = string # Defaults to 'var.policy_desc' if variable is set to null
file = string # Path to json or json.tmpl file of policy
vars = map(string) # Policy template variables {key: val, ...}
})) | `[]` | no |
99 | | users | A list of dictionaries defining all users. | list(object({
name = string # Name of the user
path = string # Defaults to 'var.user_path' if variable is set to null
groups = list(string) # List of group names to add this user to
access_keys = list(object({
name = string # IaC identifier for first or second IAM access key (not used on AWS)
pgp_key = string # Leave empty for non or provide a b64-enc pubkey or keybase username
status = string # 'Active' or 'Inactive'
}))
permissions_boundary = string # ARN to a policy used as permissions boundary (or null/empty)
policies = list(string) # List of names of policies (must be defined in var.policies)
policy_arns = list(string) # List of existing policy ARN's
inline_policies = list(object({
name = string # Name of the inline policy
file = string # Path to json or json.tmpl file of policy
vars = map(string) # Policy template variables {key = val, ...}
}))
})) | `[]` | no |
100 |
101 | ## Outputs
102 |
103 | | Name | Description |
104 | |------|-------------|
105 | | policies | Created customer managed IAM policies |
106 | | users | Created users |
107 |
108 |
109 |
--------------------------------------------------------------------------------
/examples/policies/README.md:
--------------------------------------------------------------------------------
1 | # Custom policies
2 |
3 | This example creates two policies on AWS. One simple JSON file and one with variable interpolations.
4 |
5 | ## Table of Contents
6 |
7 | 1. [Overview](#overview)
8 | 2. [Examples](#examples)
9 | - [Simple policy](#simple-policy)
10 | - [Policy with variables](#policy-with-variables)
11 | 3. [Organizing policy files](#organizing-policy-files)
12 | 4. [Use `aws_iam_policy_document` to define policies](#use-aws_iam_policy_document-to-define-policies)
13 | 5. [Usage](#usage)
14 | 6. [Requirements](#requirements)
15 | 7. [Providers](#providers)
16 | 8. [Inputs](#inputs)
17 | 9. [Outputs](#outputs)
18 |
19 |
20 | ## Overview
21 |
22 | Defined policies can be used to be attached to **[`var.groups`](../groups/)**, **[`var.users`](../users/)** and/or **[`var.roles`](../roles/)** by this module.
23 |
24 |
25 | ## Examples
26 |
27 | **Note:** The following examples only shows the creation of a single policy each.
28 | You can however create as many policies as desired. Also re-arranging them within the list will not
29 | trigger terraform to change or destroy resources as they're internally stored in a map (rather than a list) by their policy names as keys (See module's `locals.tf` for transformation).
30 |
31 | ### Simple policy
32 |
33 | The following defines a policy named `billing-ro` created under the path `/assume/`.
34 | The policy definition can be seen in the JSON file below.
35 |
36 | `terraform.tfvars`
37 | ```hcl
38 | policies = [
39 | {
40 | name = "billing-ro"
41 | path = "/assume/"
42 | desc = "Provides read-only access to billing"
43 | file = "data/policies/billing-ro.json"
44 | vars = {}
45 | }
46 | ]
47 | ```
48 |
49 | `data/policies/billing-ro.json`
50 | ```json
51 | {
52 | "Version": "2012-10-17",
53 | "Statement": [
54 | {
55 | "Sid": "BillingReadOnly",
56 | "Effect": "Allow",
57 | "Action": [
58 | "account:ListRegions",
59 | "aws-portal:View*",
60 | "awsbillingconsole:View*",
61 | "budgets:View*",
62 | "ce:Get*",
63 | "cur:Describe*",
64 | "pricing:Describe*",
65 | "pricing:Get*"
66 | ],
67 | "Resource": "*"
68 | }
69 | ]
70 | }
71 | ```
72 |
73 | ### Policy with variables
74 |
75 | The following policy allows to have variables in its json definition which might come in handy
76 | to define a single policy file, which can be used across multiple AWS accounts.
77 |
78 | The variable part will be `aws_account_id` which can differ in each environment, while still using the same policy file.
79 |
80 | `terraform.tfvars`
81 | ```hcl
82 | policies = [
83 | {
84 | name = "rds-authenticate"
85 | path = "/assume/"
86 | desc = "Allow user to authenticate to RDS via IAM"
87 | file = "data/policies/rds-authenticate.json.tmpl"
88 | vars = {
89 | aws_account_id = "1234567890",
90 | }
91 | }
92 | ]
93 | ```
94 |
95 | Terraform will automatically substitute the `aws_account_id` variable below with the corresponding value.
96 |
97 | `data/policies/rds-authenticate.json.tmpl`
98 | ```json
99 | {
100 | "Version": "2012-10-17",
101 | "Statement": [
102 | {
103 | "Sid": "RDSAuthenticationAllow",
104 | "Effect": "Allow",
105 | "Action": [
106 | "rds-db:connect"
107 | ],
108 | "Resource": [
109 | "arn:aws:rds-db:eu-central-1:${aws_account_id}:dbuser:*/iam_user_rw",
110 | "arn:aws:rds-db:eu-central-1:${aws_account_id}:dbuser:*/iam_user_ro"
111 | ]
112 | }
113 | ]
114 | }
115 | ```
116 |
117 | ## Organizing policy files
118 |
119 | I would recommend to keep all policies at a single place of truth and have them defined in various sub directories depending if they are generic or specific.
120 |
121 | ```bash
122 | .
123 | └── policies
124 | ├── account-dev
125 | │ ├── user-developer.json
126 | │ ├── user-devops.json
127 | │ └── user-manager.json
128 | ├── account-prod
129 | │ ├── user-developer.json
130 | │ ├── user-devops.json
131 | │ └── user-manager.json
132 | └── generic
133 | ├── billing-ro.json
134 | ├── iam-create-service-role.json
135 | ├── kms-ro.json
136 | ├── kms-rw.json
137 | ├── rds-authenticate.json
138 | ├── sns-ro.json
139 | ├── sns-rw.json
140 | ├── sqs-ro.json
141 | └── sqs-rw.json
142 | ```
143 |
144 | You can then simply symlink the `policies/` directory into each of your environments terraform or terragrunt directories.
145 |
146 |
147 |
148 | ## Use `aws_iam_policy_document` to define policies
149 |
150 | Using JSON policies with variables offers some sort of flexibility, but terraform's data source [`aws_iam_policy_document`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) offers even more flexibility, as you can gather specific resources first and then use them within the policy definition.
151 |
152 | So is it possible to also define policies with [`aws_iam_policy_document`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) and then attach them to groups, users and/or roles in this module?
153 |
154 | **Yes!** See the following example for how to achieve this **[Policies with custom data sources](../policies-with-custom-data-sources)**
155 |
156 |
157 | ## Usage
158 |
159 | To run this example you need to execute:
160 |
161 | ```bash
162 | $ terraform init
163 | $ terraform plan
164 | $ terraform apply
165 | ```
166 |
167 | Note that this example may create resources which cost money. Run terraform destroy when you don't need these resources.
168 |
169 |
170 |
171 | ## Requirements
172 |
173 | No requirements.
174 |
175 | ## Providers
176 |
177 | No provider.
178 |
179 | ## Inputs
180 |
181 | | Name | Description | Type | Default | Required |
182 | |------|-------------|------|---------|:--------:|
183 | | policies | A list of dictionaries defining all policies. | list(object({
name = string # Name of the policy
path = string # Defaults to 'var.policy_path' if variable is set to null
desc = string # Defaults to 'var.policy_desc' if variable is set to null
file = string # Path to json or json.tmpl file of policy
vars = map(string) # Policy template variables {key: val, ...}
})) | `[]` | no |
184 |
185 | ## Outputs
186 |
187 | | Name | Description |
188 | |------|-------------|
189 | | policies | Created customer managed IAM policies |
190 |
191 |
192 |
--------------------------------------------------------------------------------
/examples/groups-users-and-policies/README.md:
--------------------------------------------------------------------------------
1 | # Groups, Users and Policies
2 |
3 | This example creates policies, groups and users.
4 |
5 |
6 | ## Usage
7 |
8 | To run this example you need to execute:
9 |
10 | ```bash
11 | $ terraform init
12 | $ terraform plan
13 | $ terraform apply
14 | ```
15 |
16 | Note that this example may create resources which cost money. Run terraform destroy when you don't need these resources.
17 |
18 |
19 |
20 | ## Requirements
21 |
22 | No requirements.
23 |
24 | ## Providers
25 |
26 | No provider.
27 |
28 | ## Inputs
29 |
30 | | Name | Description | Type | Default | Required |
31 | |------|-------------|------|---------|:--------:|
32 | | account\_alias | Assign the account alias for the AWS Account. Unmanaged by default. Resource will be created if the string is non-empty. | `string` | `""` | no |
33 | | account\_pass\_policy | Manages Password Policy for the AWS Account. Unmanaged by default. Resource will be created if 'manage' is set to true. | object({
manage = bool # Set to true, to manage the AWS account password policy
allow_users_to_change_password = bool # Allow users to change their own password?
hard_expiry = bool # Users are prevented from setting a new password after their password has expired?
max_password_age = number # Number of days that an user password is valid
minimum_password_length = number # Minimum length to require for user passwords
password_reuse_prevention = number # The number of previous passwords that users are prevented from reusing
require_lowercase_characters = bool # Require lowercase characters for user passwords?
require_numbers = bool # Require numbers for user passwords?
require_symbols = bool # Require symbols for user passwords?
require_uppercase_characters = bool # Require uppercase characters for user passwords?
}) | {
"allow_users_to_change_password": null,
"hard_expiry": null,
"manage": false,
"max_password_age": null,
"minimum_password_length": null,
"password_reuse_prevention": null,
"require_lowercase_characters": null,
"require_numbers": null,
"require_symbols": null,
"require_uppercase_characters": null
} | no |
34 | | providers\_saml | A list of dictionaries defining saml providers. | list(object({
name = string # The name of the provider to create
file = string # Path to XML generated by identity provider that supports SAML 2.0
})) | `[]` | no |
35 | | providers\_oidc | A list of dictionaries defining openid connect providers. | list(object({
url = string # URL of the identity provider. Corresponds to the iss claim
client_id_list = list(string) # List of client IDs (also known as audiences)
thumbprint_list = list(string) # List of server certificate thumbprints.
})) | `[]` | no |
36 | | policies | A list of dictionaries defining all policies. | list(object({
name = string # Name of the policy
path = string # Defaults to 'var.policy_path' if variable is set to null
desc = string # Defaults to 'var.policy_desc' if variable is set to null
file = string # Path to json or json.tmpl file of policy
vars = map(string) # Policy template variables {key: val, ...}
})) | `[]` | no |
37 | | groups | A list of dictionaries defining all groups. | list(object({
name = string # Name of the group
path = string # Defaults to 'var.group_path' if variable is set to null
policies = list(string) # List of names of policies (must be defined in var.policies)
policy_arns = list(string) # List of existing policy ARN's
inline_policies = list(object({
name = string # Name of the inline policy
file = string # Path to json or json.tmpl file of policy
vars = map(string) # Policy template variables {key = val, ...}
}))
})) | `[]` | no |
38 | | users | A list of dictionaries defining all users. | list(object({
name = string # Name of the user
path = string # Defaults to 'var.user_path' if variable is set to null
groups = list(string) # List of group names to add this user to
access_keys = list(object({
name = string # IaC identifier for first or second IAM access key (not used on AWS)
pgp_key = string # Leave empty for non or provide a b64-enc pubkey or keybase username
status = string # 'Active' or 'Inactive'
}))
permissions_boundary = string # ARN to a policy used as permissions boundary (or null/empty)
policies = list(string) # List of names of policies (must be defined in var.policies)
policy_arns = list(string) # List of existing policy ARN's
inline_policies = list(object({
name = string # Name of the inline policy
file = string # Path to json or json.tmpl file of policy
vars = map(string) # Policy template variables {key = val, ...}
}))
})) | `[]` | no |
39 | | roles | A list of dictionaries defining all roles. | list(object({
name = string # Name of the role
path = string # Defaults to 'var.role_path' if variable is set to null
desc = string # Defaults to 'var.role_desc' if variable is set to null
trust_policy_file = string # Path to file of trust/assume policy
permissions_boundary = string # ARN to a policy used as permissions boundary (or null/empty)
policies = list(string) # List of names of policies (must be defined in var.policies)
policy_arns = list(string) # List of existing policy ARN's
inline_policies = list(object({
name = string # Name of the inline policy
file = string # Path to json or json.tmpl file of policy
vars = map(string) # Policy template variables {key = val, ...}
}))
})) | `[]` | no |
40 | | policy\_path | The default path under which to create the policy if not specified in the policies list. You can use a single path, or nest multiple paths as if they were a folder structure. For example, you could use the nested path /division\_abc/subdivision\_xyz/product\_1234/engineering/ to match your company's organizational structure. | `string` | `"/"` | no |
41 | | policy\_desc | The default description of the policy. | `string` | `"Managed by Terraform"` | no |
42 | | group\_path | The path under which to create the group. You can use a single path, or nest multiple paths as if they were a folder structure. For example, you could use the nested path /division\_abc/subdivision\_xyz/product\_1234/engineering/ to match your company's organizational structure. | `string` | `"/"` | no |
43 | | user\_path | The path under which to create the user. You can use a single path, or nest multiple paths as if they were a folder structure. For example, you could use the nested path /division\_abc/subdivision\_xyz/product\_1234/engineering/ to match your company's organizational structure. | `string` | `"/"` | no |
44 | | role\_path | The path under which to create the role. You can use a single path, or nest multiple paths as if they were a folder structure. For example, you could use the nested path /division\_abc/subdivision\_xyz/product\_1234/engineering/ to match your company's organizational structure. | `string` | `"/"` | no |
45 | | role\_desc | The description of the role. | `string` | `"Managed by Terraform"` | no |
46 | | role\_max\_session\_duration | The maximum session duration (in seconds) that you want to set for the specified role. This setting can have a value from 1 hour to 12 hours specified in seconds. | `string` | `"3600"` | no |
47 | | role\_force\_detach\_policies | Specifies to force detaching any policies the role has before destroying it. | `bool` | `true` | no |
48 | | tags | Key-value mapping of tags for the IAM role or user. | `map(any)` | `{}` | no |
49 |
50 | ## Outputs
51 |
52 | | Name | Description |
53 | |------|-------------|
54 | | policies | Created customer managed IAM policies |
55 | | groups | Created groups |
56 | | users | Created users |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | ifneq (,)
2 | .error This Makefile requires GNU Make.
3 | endif
4 |
5 | .PHONY: help gen lint test _gen-main _gen-examples _gen-modules _lint-files _lint-fmt _lint-json _pull-tf _pull-tfdocs _pull-fl _pull-jl
6 |
7 | CURRENT_DIR = $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
8 | TF_EXAMPLES = $(sort $(dir $(wildcard $(CURRENT_DIR)examples/*/)))
9 | TF_MODULES = $(sort $(dir $(wildcard $(CURRENT_DIR)modules/*/)))
10 |
11 | # -------------------------------------------------------------------------------------------------
12 | # Container versions
13 | # -------------------------------------------------------------------------------------------------
14 | TF_VERSION = light
15 | TFDOCS_VERSION = 0.10.1
16 | FL_VERSION = 0.3
17 | JL_VERSION = latest-0.4
18 |
19 |
20 | # -------------------------------------------------------------------------------------------------
21 | # Enable linter (file-lint, terraform fmt, jsonlint)
22 | # -------------------------------------------------------------------------------------------------
23 | LINT_FL_ENABLE = 1
24 | LINT_TF_ENABLE = 1
25 | LINT_JL_ENABLE = 1
26 |
27 |
28 | # -------------------------------------------------------------------------------------------------
29 | # terraform-docs defines
30 | # -------------------------------------------------------------------------------------------------
31 | # Adjust your delimiter here or overwrite via make arguments
32 | DELIM_START =
33 | DELIM_CLOSE =
34 | # What arguments to append to terraform-docs command
35 | TFDOCS_ARGS = --sort=false
36 |
37 |
38 | # -------------------------------------------------------------------------------------------------
39 | # Default target
40 | # -------------------------------------------------------------------------------------------------
41 | help:
42 | @echo "gen Generate terraform-docs output and replace in README.md's"
43 | @echo "lint Static source code analysis"
44 | @echo "test Integration tests"
45 |
46 |
47 | # -------------------------------------------------------------------------------------------------
48 | # Standard targets
49 | # -------------------------------------------------------------------------------------------------
50 | gen: _pull-tfdocs
51 | @echo "################################################################################"
52 | @echo "# Terraform-docs generate"
53 | @echo "################################################################################"
54 | @$(MAKE) --no-print-directory _gen-main
55 | @$(MAKE) --no-print-directory _gen-examples
56 | @$(MAKE) --no-print-directory _gen-modules
57 |
58 | lint:
59 | @if [ "$(LINT_FL_ENABLE)" = "1" ]; then \
60 | $(MAKE) --no-print-directory _lint-files; \
61 | fi
62 | @if [ "$(LINT_TF_ENABLE)" = "1" ]; then \
63 | $(MAKE) --no-print-directory _lint-fmt; \
64 | fi
65 | @if [ "$(LINT_JL_ENABLE)" = "1" ]; then \
66 | $(MAKE) --no-print-directory _lint-json; \
67 | fi
68 |
69 | test: _pull-tf
70 | @$(foreach example,\
71 | $(TF_EXAMPLES),\
72 | DOCKER_PATH="/t/examples/$(notdir $(patsubst %/,%,$(example)))"; \
73 | echo "################################################################################"; \
74 | echo "# examples/$$( basename $${DOCKER_PATH} )"; \
75 | echo "################################################################################"; \
76 | echo; \
77 | echo "------------------------------------------------------------"; \
78 | echo "# Terraform init"; \
79 | echo "------------------------------------------------------------"; \
80 | if docker run $$(tty -s && echo "-it" || echo) --rm -v "$(CURRENT_DIR):/t" --workdir "$${DOCKER_PATH}" hashicorp/terraform:$(TF_VERSION) \
81 | init \
82 | -verify-plugins=true \
83 | -lock=false \
84 | -upgrade=true \
85 | -reconfigure \
86 | -input=false \
87 | -get-plugins=true \
88 | -get=true \
89 | .; then \
90 | echo "OK"; \
91 | else \
92 | echo "Failed"; \
93 | docker run $$(tty -s && echo "-it" || echo) --rm -v "$(CURRENT_DIR):/t" --workdir "$${DOCKER_PATH}" --entrypoint=rm hashicorp/terraform:$(TF_VERSION) -rf .terraform/ || true; \
94 | exit 1; \
95 | fi; \
96 | echo; \
97 | echo "------------------------------------------------------------"; \
98 | echo "# Terraform validate"; \
99 | echo "------------------------------------------------------------"; \
100 | if docker run $$(tty -s && echo "-it" || echo) --rm -v "$(CURRENT_DIR):/t" --workdir "$${DOCKER_PATH}" hashicorp/terraform:$(TF_VERSION) \
101 | validate \
102 | $(ARGS) \
103 | .; then \
104 | echo "OK"; \
105 | docker run $$(tty -s && echo "-it" || echo) --rm -v "$(CURRENT_DIR):/t" --workdir "$${DOCKER_PATH}" --entrypoint=rm hashicorp/terraform:$(TF_VERSION) -rf .terraform/ || true; \
106 | else \
107 | echo "Failed"; \
108 | docker run $$(tty -s && echo "-it" || echo) --rm -v "$(CURRENT_DIR):/t" --workdir "$${DOCKER_PATH}" --entrypoint=rm hashicorp/terraform:$(TF_VERSION) -rf .terraform/ || true; \
109 | exit 1; \
110 | fi; \
111 | echo; \
112 | )
113 |
114 |
115 | # -------------------------------------------------------------------------------------------------
116 | # Helper Targets
117 | # -------------------------------------------------------------------------------------------------
118 | _gen-main:
119 | @echo "------------------------------------------------------------"
120 | @echo "# Main module"
121 | @echo "------------------------------------------------------------"
122 | @if docker run $$(tty -s && echo "-it" || echo) --rm \
123 | -v $(CURRENT_DIR):/data \
124 | -e DELIM_START='' \
125 | -e DELIM_CLOSE='' \
126 | cytopia/terraform-docs:$(TFDOCS_VERSION) \
127 | terraform-docs-replace-012 --show-all=false --show inputs md doc --indent 2 $(TFDOCS_ARGS) README.md; then \
128 | echo "OK"; \
129 | else \
130 | echo "Failed"; \
131 | exit 1; \
132 | fi
133 | @if docker run $$(tty -s && echo "-it" || echo) --rm \
134 | -v $(CURRENT_DIR):/data \
135 | -e DELIM_START='' \
136 | -e DELIM_CLOSE='' \
137 | cytopia/terraform-docs:$(TFDOCS_VERSION) \
138 | terraform-docs-replace-012 --show-all=false --show outputs md tbl --indent 2 --sort README.md; then \
139 | echo "OK"; \
140 | else \
141 | echo "Failed"; \
142 | exit 1; \
143 | fi
144 |
145 | _gen-examples:
146 | @$(foreach example,\
147 | $(TF_EXAMPLES),\
148 | DOCKER_PATH="examples/$(notdir $(patsubst %/,%,$(example)))"; \
149 | echo "------------------------------------------------------------"; \
150 | echo "# $${DOCKER_PATH}"; \
151 | echo "------------------------------------------------------------"; \
152 | if docker run $$(tty -s && echo "-it" || echo) --rm \
153 | -v $(CURRENT_DIR):/data \
154 | -e DELIM_START='$(DELIM_START)' \
155 | -e DELIM_CLOSE='$(DELIM_CLOSE)' \
156 | cytopia/terraform-docs:$(TFDOCS_VERSION) \
157 | terraform-docs-replace-012 $(TFDOCS_ARGS) md $${DOCKER_PATH}/README.md; then \
158 | echo "OK"; \
159 | else \
160 | echo "Failed"; \
161 | exit 1; \
162 | fi; \
163 | )
164 |
165 | _gen-modules:
166 | @$(foreach module,\
167 | $(TF_MODULES),\
168 | DOCKER_PATH="modules/$(notdir $(patsubst %/,%,$(module)))"; \
169 | echo "------------------------------------------------------------"; \
170 | echo "# $${DOCKER_PATH}"; \
171 | echo "------------------------------------------------------------"; \
172 | if docker run $$(tty -s && echo "-it" || echo) --rm \
173 | -v $(CURRENT_DIR):/data \
174 | -e DELIM_START='$(DELIM_START)' \
175 | -e DELIM_CLOSE='$(DELIM_CLOSE)' \
176 | cytopia/terraform-docs:$(TFDOCS_VERSION) \
177 | terraform-docs-replace-012 $(TFDOCS_ARGS) md $${DOCKER_PATH}/README.md; then \
178 | echo "OK"; \
179 | else \
180 | echo "Failed"; \
181 | exit 1; \
182 | fi; \
183 | )
184 |
185 | _lint-files: _pull-fl
186 | @# Basic file linting
187 | @echo "################################################################################"
188 | @echo "# File-lint"
189 | @echo "################################################################################"
190 | @docker run $$(tty -s && echo "-it" || echo) --rm -v $(CURRENT_DIR):/data cytopia/file-lint:$(FL_VERSION) file-cr --text --ignore '.git/,.github/,.terraform/' --path .
191 | @docker run $$(tty -s && echo "-it" || echo) --rm -v $(CURRENT_DIR):/data cytopia/file-lint:$(FL_VERSION) file-crlf --text --ignore '.git/,.github/,.terraform/' --path .
192 | @docker run $$(tty -s && echo "-it" || echo) --rm -v $(CURRENT_DIR):/data cytopia/file-lint:$(FL_VERSION) file-trailing-single-newline --text --ignore '.git/,.github/,.terraform/' --path .
193 | @docker run $$(tty -s && echo "-it" || echo) --rm -v $(CURRENT_DIR):/data cytopia/file-lint:$(FL_VERSION) file-trailing-space --text --ignore '.git/,.github/,.terraform/' --path .
194 | @docker run $$(tty -s && echo "-it" || echo) --rm -v $(CURRENT_DIR):/data cytopia/file-lint:$(FL_VERSION) file-utf8 --text --ignore '.git/,.github/,.terraform/' --path .
195 | @docker run $$(tty -s && echo "-it" || echo) --rm -v $(CURRENT_DIR):/data cytopia/file-lint:$(FL_VERSION) file-utf8-bom --text --ignore '.git/,.github/,.terraform/' --path .
196 |
197 | _lint-fmt: _pull-tf
198 | @# Lint all Terraform files
199 | @echo "################################################################################"
200 | @echo "# Terraform fmt"
201 | @echo "################################################################################"
202 | @echo
203 | @echo "------------------------------------------------------------"
204 | @echo "# *.tf files"
205 | @echo "------------------------------------------------------------"
206 | @if docker run $$(tty -s && echo "-it" || echo) --rm -v "$(CURRENT_DIR):/t:ro" --workdir "/t" hashicorp/terraform:$(TF_VERSION) \
207 | fmt -check=true -diff=true -write=false -list=true .; then \
208 | echo "OK"; \
209 | else \
210 | echo "Failed"; \
211 | exit 1; \
212 | fi;
213 | @echo
214 | @echo "------------------------------------------------------------"
215 | @echo "# *.tfvars files"
216 | @echo "------------------------------------------------------------"
217 | @if docker run $$(tty -s && echo "-it" || echo) --rm --entrypoint=/bin/sh -v "$(CURRENT_DIR):/t:ro" --workdir "/t" hashicorp/terraform:$(TF_VERSION) \
218 | -c "find . -name '*.tfvars' -type f -print0 | xargs -0 -n1 terraform fmt -check=true -write=false -diff=true -list=true"; then \
219 | echo "OK"; \
220 | else \
221 | echo "Failed"; \
222 | exit 1; \
223 | fi;
224 | @echo
225 |
226 | _lint-json: _pull-jl
227 | @# Lint all JSON files
228 | @echo "################################################################################"
229 | @echo "# Jsonlint"
230 | @echo "################################################################################"
231 | @if docker run $$(tty -s && echo "-it" || echo) --rm -v "$(CURRENT_DIR):/data:ro" cytopia/jsonlint:$(JL_VERSION) \
232 | -t ' ' -i '*.terraform/*' '*.json'; then \
233 | echo "OK"; \
234 | else \
235 | echo "Failed"; \
236 | exit 1; \
237 | fi;
238 | @echo
239 |
240 | _pull-tf:
241 | docker pull hashicorp/terraform:$(TF_VERSION)
242 |
243 | _pull-tfdocs:
244 | docker pull cytopia/terraform-docs:$(TFDOCS_VERSION)
245 |
246 | _pull-fl:
247 | docker pull cytopia/file-lint:$(FL_VERSION)
248 |
--------------------------------------------------------------------------------
/main.tf:
--------------------------------------------------------------------------------
1 | # -------------------------------------------------------------------------------------------------
2 | # Set module requirements
3 | # -------------------------------------------------------------------------------------------------
4 |
5 | terraform {
6 | # >= v0.12.6
7 | required_version = ">= 0.12.6"
8 | }
9 |
10 |
11 | # -------------------------------------------------------------------------------------------------
12 | # 1. Account Settings
13 | # -------------------------------------------------------------------------------------------------
14 |
15 | # Create account alias (if not empty)
16 | resource "aws_iam_account_alias" "default" {
17 | count = var.account_alias != "" ? 1 : 0
18 |
19 | account_alias = var.account_alias
20 | }
21 |
22 | # Setup account password policy
23 | resource "aws_iam_account_password_policy" "default" {
24 | count = var.account_pass_policy.manage == true ? 1 : 0
25 |
26 | allow_users_to_change_password = var.account_pass_policy.allow_users_to_change_password
27 | hard_expiry = var.account_pass_policy.hard_expiry
28 | max_password_age = var.account_pass_policy.max_password_age
29 | minimum_password_length = var.account_pass_policy.minimum_password_length
30 | password_reuse_prevention = var.account_pass_policy.password_reuse_prevention
31 | require_lowercase_characters = var.account_pass_policy.require_lowercase_characters
32 | require_numbers = var.account_pass_policy.require_numbers
33 | require_symbols = var.account_pass_policy.require_symbols
34 | require_uppercase_characters = var.account_pass_policy.require_uppercase_characters
35 | }
36 |
37 |
38 | # -------------------------------------------------------------------------------------------------
39 | # 2. Identity Providers
40 | # -------------------------------------------------------------------------------------------------
41 |
42 | # Create SAML providers
43 | resource "aws_iam_saml_provider" "default" {
44 | for_each = { for saml in var.providers_saml : saml.name => saml }
45 |
46 | name = each.value.name
47 | saml_metadata_document = file(each.value.file)
48 | }
49 |
50 | # Create OpenID Connect providers
51 | resource "aws_iam_openid_connect_provider" "default" {
52 | for_each = { for oidc in var.providers_oidc : md5(oidc.url) => oidc }
53 |
54 | url = each.value.url
55 | client_id_list = each.value.client_id_list
56 | thumbprint_list = each.value.thumbprint_list
57 | }
58 |
59 |
60 | # -------------------------------------------------------------------------------------------------
61 | # 3. Policies
62 | # -------------------------------------------------------------------------------------------------
63 |
64 | # Create customer managed policies
65 | resource "aws_iam_policy" "policies" {
66 | for_each = local.policies
67 |
68 | name = lookup(each.value, "name")
69 | path = lookup(each.value, "path", null) == null ? var.policy_path : lookup(each.value, "path")
70 | description = lookup(each.value, "desc", null) == null ? var.policy_desc : lookup(each.value, "desc")
71 | policy = templatefile(lookup(each.value, "file"), lookup(each.value, "vars"))
72 | }
73 |
74 |
75 | # -------------------------------------------------------------------------------------------------
76 | # 4. Groups
77 | # -------------------------------------------------------------------------------------------------
78 |
79 | # Create groups
80 | resource "aws_iam_group" "groups" {
81 | for_each = { for group in var.groups : group.name => group }
82 |
83 | name = lookup(each.value, "name")
84 | path = lookup(each.value, "path", null) == null ? var.group_path : lookup(each.value, "path")
85 | }
86 |
87 | # Attach customer managed policies to group
88 | resource "aws_iam_group_policy_attachment" "policy_attachments" {
89 | for_each = local.group_policies
90 |
91 | group = replace(each.key, format(":%s", each.value.name), "")
92 | policy_arn = aws_iam_policy.policies[each.value.name].arn
93 |
94 | # Terraform has no info that aws_iam_users and aws_iam_policies
95 | # must be run first in order to create the groups,
96 | # so we must explicitly tell it.
97 | depends_on = [
98 | aws_iam_group.groups,
99 | aws_iam_policy.policies,
100 | ]
101 | }
102 |
103 | # Attach policy ARNs to group
104 | resource "aws_iam_group_policy_attachment" "policy_arn_attachments" {
105 | for_each = local.group_policy_arns
106 |
107 | group = replace(each.key, format(":%s", each.value), "")
108 | policy_arn = each.value
109 |
110 | # Terraform has no info that aws_iam_users must be run first in order to create the groups,
111 | # so we must explicitly tell it.
112 | depends_on = [aws_iam_group.groups]
113 | }
114 |
115 | # Attach inline policies to group
116 | resource "aws_iam_group_policy" "inline_policy_attachments" {
117 | for_each = local.group_inline_policies
118 |
119 | name = each.value.name
120 | group = replace(each.key, format(":%s", each.value.name), "")
121 | policy = templatefile(lookup(each.value, "file"), lookup(each.value, "vars"))
122 |
123 | # Terraform has no info that aws_iam_users must be run first in order to create the users,
124 | # so we must explicitly tell it.
125 | depends_on = [aws_iam_group.groups]
126 | }
127 |
128 |
129 |
130 | # -------------------------------------------------------------------------------------------------
131 | # 5. Users
132 | # -------------------------------------------------------------------------------------------------
133 |
134 | # Create users
135 | resource "aws_iam_user" "users" {
136 | for_each = { for user in var.users : user.name => user }
137 |
138 | name = lookup(each.value, "name")
139 | path = lookup(each.value, "path", null) == null ? var.user_path : lookup(each.value, "path")
140 |
141 | # The boundary defines the maximum allowed permissions which cannot exceed.
142 | # Even if the policy has higher permission, the boundary sets the final limit
143 | permissions_boundary = each.value.permissions_boundary
144 |
145 | tags = merge(
146 | map("Name", lookup(each.value, "name")),
147 | var.tags
148 | )
149 | }
150 |
151 | # Attach customer managed policies to user
152 | resource "aws_iam_user_policy_attachment" "policy_attachments" {
153 | for_each = local.user_policies
154 |
155 | user = replace(each.key, format(":%s", each.value.name), "")
156 | policy_arn = aws_iam_policy.policies[each.value.name].arn
157 |
158 | # Terraform has no info that aws_iam_users and aws_iam_policies
159 | # must be run first in order to create the users,
160 | # so we must explicitly tell it.
161 | depends_on = [
162 | aws_iam_user.users,
163 | aws_iam_policy.policies,
164 | ]
165 | }
166 |
167 | # Attach policy ARNs to user
168 | resource "aws_iam_user_policy_attachment" "policy_arn_attachments" {
169 | for_each = local.user_policy_arns
170 |
171 | user = replace(each.key, format(":%s", each.value), "")
172 | policy_arn = each.value
173 |
174 | # Terraform has no info that aws_iam_users must be run first in order to create the users,
175 | # so we must explicitly tell it.
176 | depends_on = [aws_iam_user.users]
177 | }
178 |
179 | # Attach inline policies to user
180 | resource "aws_iam_user_policy" "inline_policy_attachments" {
181 | for_each = local.user_inline_policies
182 |
183 | name = each.value.name
184 | user = replace(each.key, format(":%s", each.value.name), "")
185 | policy = templatefile(lookup(each.value, "file"), lookup(each.value, "vars"))
186 |
187 | # Terraform has no info that aws_iam_users must be run first in order to create the users,
188 | # so we must explicitly tell it.
189 | depends_on = [aws_iam_user.users]
190 | }
191 |
192 | # Add 'Active' or 'Inactive' access key to an IAM user
193 | resource "aws_iam_access_key" "access_key" {
194 | for_each = local.user_access_keys
195 |
196 | user = split(":", each.key)[0]
197 | pgp_key = each.value.pgp_key
198 | status = each.value.status
199 |
200 | # Terraform has no info that aws_iam_users must be run first in order to create the users,
201 | # so we must explicitly tell it.
202 | depends_on = [aws_iam_user.users]
203 | }
204 |
205 | # Add users to groups
206 | resource "aws_iam_user_group_membership" "group_membership" {
207 | # Note: Only iterate over users which actually have a group specified
208 | for_each = { for user in var.users : user.name => user if length(user.groups) > 0 }
209 |
210 | user = each.value.name
211 | groups = each.value.groups
212 |
213 | # Terraform has no info that aws_iam_user|group must be run first in order to create the
214 | # attachment, so we must explicitly tell it.
215 | depends_on = [
216 | aws_iam_user.users,
217 | aws_iam_group.groups,
218 | ]
219 | }
220 |
221 |
222 | # -------------------------------------------------------------------------------------------------
223 | # 6. Roles
224 | # -------------------------------------------------------------------------------------------------
225 |
226 | # Create roles
227 | resource "aws_iam_role" "roles" {
228 | for_each = { for role in var.roles : role.name => role }
229 |
230 | name = lookup(each.value, "name")
231 | path = lookup(each.value, "path", null) == null ? var.role_path : lookup(each.value, "path")
232 | description = lookup(each.value, "desc", null) == null ? var.role_desc : lookup(each.value, "desc")
233 |
234 | # This policy defines who/what is allowed to use the current role
235 | assume_role_policy = file(lookup(each.value, "trust_policy_file"))
236 |
237 | # The boundary defines the maximum allowed permissions which cannot exceed.
238 | # Even if the policy has higher permission, the boundary sets the final limit
239 | permissions_boundary = each.value.permissions_boundary
240 |
241 | # Allow session for X seconds
242 | max_session_duration = var.role_max_session_duration
243 | force_detach_policies = var.role_force_detach_policies
244 |
245 | tags = merge(
246 | map("Name", lookup(each.value, "name")),
247 | var.tags
248 | )
249 | }
250 |
251 | # Attach customer managed policies to roles
252 | resource "aws_iam_role_policy_attachment" "policy_attachments" {
253 | for_each = local.role_policies
254 |
255 | role = replace(each.key, format(":%s", each.value.name), "")
256 | policy_arn = aws_iam_policy.policies[each.value.name].arn
257 |
258 | # Terraform has no info that aws_iam_roles and aws_iam_policies
259 | # must be run first in order to create the roles,
260 | # so we must explicitly tell it.
261 | depends_on = [
262 | aws_iam_role.roles,
263 | aws_iam_policy.policies,
264 | ]
265 | }
266 |
267 | # Attach policy ARNs to roles
268 | resource "aws_iam_role_policy_attachment" "policy_arn_attachments" {
269 | for_each = local.role_policy_arns
270 |
271 | role = replace(each.key, format(":%s", each.value), "")
272 | policy_arn = each.value
273 |
274 | # Terraform has no info that aws_iam_roles must be run first in order to create the roles,
275 | # so we must explicitly tell it.
276 | depends_on = [aws_iam_role.roles]
277 | }
278 |
279 | # Attach inline policies to roles
280 | resource "aws_iam_role_policy" "inline_policy_attachments" {
281 | for_each = local.role_inline_policies
282 |
283 | name = each.value.name
284 | role = replace(each.key, format(":%s", each.value.name), "")
285 | policy = templatefile(lookup(each.value, "file"), lookup(each.value, "vars"))
286 |
287 | # Terraform has no info that aws_iam_roles must be run first in order to create the roles,
288 | # so we must explicitly tell it.
289 | depends_on = [aws_iam_role.roles]
290 | }
291 |
--------------------------------------------------------------------------------
/locals.tf:
--------------------------------------------------------------------------------
1 | # -------------------------------------------------------------------------------------------------
2 | # Policy transformations
3 | # -------------------------------------------------------------------------------------------------
4 |
5 | locals {
6 | # Transforn from:
7 | #
8 | # policies = [
9 | # {
10 | # name = "object({
manage = bool # Set to true, to manage the AWS account password policy
allow_users_to_change_password = bool # Allow users to change their own password?
hard_expiry = bool # Users are prevented from setting a new password after their password has expired?
max_password_age = number # Number of days that an user password is valid
minimum_password_length = number # Minimum length to require for user passwords
password_reuse_prevention = number # The number of previous passwords that users are prevented from reusing
require_lowercase_characters = bool # Require lowercase characters for user passwords?
require_numbers = bool # Require numbers for user passwords?
require_symbols = bool # Require symbols for user passwords?
require_uppercase_characters = bool # Require uppercase characters for user passwords?
}) | {
"allow_users_to_change_password": null,
"hard_expiry": null,
"manage": false,
"max_password_age": null,
"minimum_password_length": null,
"password_reuse_prevention": null,
"require_lowercase_characters": null,
"require_numbers": null,
"require_symbols": null,
"require_uppercase_characters": null
} | no |
181 | | providers\_saml | A list of dictionaries defining saml providers. | list(object({
name = string # The name of the provider to create
file = string # Path to XML generated by identity provider that supports SAML 2.0
})) | `[]` | no |
182 | | providers\_oidc | A list of dictionaries defining openid connect providers. | list(object({
url = string # URL of the identity provider. Corresponds to the iss claim
client_id_list = list(string) # List of client IDs (also known as audiences)
thumbprint_list = list(string) # List of server certificate thumbprints.
})) | `[]` | no |
183 | | policies | A list of dictionaries defining all policies. | list(object({
name = string # Name of the policy
path = string # Defaults to 'var.policy_path' if variable is set to null
desc = string # Defaults to 'var.policy_desc' if variable is set to null
file = string # Path to json or json.tmpl file of policy
vars = map(string) # Policy template variables {key: val, ...}
})) | `[]` | no |
184 | | groups | A list of dictionaries defining all groups. | list(object({
name = string # Name of the group
path = string # Defaults to 'var.group_path' if variable is set to null
policies = list(string) # List of names of policies (must be defined in var.policies)
policy_arns = list(string) # List of existing policy ARN's
inline_policies = list(object({
name = string # Name of the inline policy
file = string # Path to json or json.tmpl file of policy
vars = map(string) # Policy template variables {key = val, ...}
}))
})) | `[]` | no |
185 | | users | A list of dictionaries defining all users. | list(object({
name = string # Name of the user
path = string # Defaults to 'var.user_path' if variable is set to null
groups = list(string) # List of group names to add this user to
access_keys = list(object({
name = string # IaC identifier for first or second IAM access key (not used on AWS)
pgp_key = string # Leave empty for non or provide a b64-enc pubkey or keybase username
status = string # 'Active' or 'Inactive'
}))
permissions_boundary = string # ARN to a policy used as permissions boundary (or null/empty)
policies = list(string) # List of names of policies (must be defined in var.policies)
policy_arns = list(string) # List of existing policy ARN's
inline_policies = list(object({
name = string # Name of the inline policy
file = string # Path to json or json.tmpl file of policy
vars = map(string) # Policy template variables {key = val, ...}
}))
})) | `[]` | no |
186 | | roles | A list of dictionaries defining all roles. | list(object({
name = string # Name of the role
path = string # Defaults to 'var.role_path' if variable is set to null
desc = string # Defaults to 'var.role_desc' if variable is set to null
trust_policy_file = string # Path to file of trust/assume policy
permissions_boundary = string # ARN to a policy used as permissions boundary (or null/empty)
policies = list(string) # List of names of policies (must be defined in var.policies)
policy_arns = list(string) # List of existing policy ARN's
inline_policies = list(object({
name = string # Name of the inline policy
file = string # Path to json or json.tmpl file of policy
vars = map(string) # Policy template variables {key = val, ...}
}))
})) | `[]` | no |
187 | | policy\_path | The default path under which to create the policy if not specified in the policies list. You can use a single path, or nest multiple paths as if they were a folder structure. For example, you could use the nested path /division\_abc/subdivision\_xyz/product\_1234/engineering/ to match your company's organizational structure. | `string` | `"/"` | no |
188 | | policy\_desc | The default description of the policy. | `string` | `"Managed by Terraform"` | no |
189 | | group\_path | The path under which to create the group. You can use a single path, or nest multiple paths as if they were a folder structure. For example, you could use the nested path /division\_abc/subdivision\_xyz/product\_1234/engineering/ to match your company's organizational structure. | `string` | `"/"` | no |
190 | | user\_path | The path under which to create the user. You can use a single path, or nest multiple paths as if they were a folder structure. For example, you could use the nested path /division\_abc/subdivision\_xyz/product\_1234/engineering/ to match your company's organizational structure. | `string` | `"/"` | no |
191 | | role\_path | The path under which to create the role. You can use a single path, or nest multiple paths as if they were a folder structure. For example, you could use the nested path /division\_abc/subdivision\_xyz/product\_1234/engineering/ to match your company's organizational structure. | `string` | `"/"` | no |
192 | | role\_desc | The description of the role. | `string` | `"Managed by Terraform"` | no |
193 | | role\_max\_session\_duration | The maximum session duration (in seconds) that you want to set for the specified role. This setting can have a value from 1 hour to 12 hours specified in seconds. | `string` | `"3600"` | no |
194 | | role\_force\_detach\_policies | Specifies to force detaching any policies the role has before destroying it. | `bool` | `true` | no |
195 | | tags | Key-value mapping of tags for the IAM role or user. | `map(any)` | `{}` | no |
196 |
197 | ## Outputs
198 |
199 | | Name | Description |
200 | |------|-------------|
201 | | policies | Created customer managed IAM policies |
202 | | roles | Created roles |
203 | | providers\_saml | Created SAML providers. |
204 |
205 |
206 |
--------------------------------------------------------------------------------
/examples/policies-with-custom-data-sources/README.md:
--------------------------------------------------------------------------------
1 | # Policies with custom data sources
2 |
3 | This example creates a policy via terraforms [`aws_iam_policy_document`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) and then adds it to a role's `policy_arns` list.
4 |
5 |
6 | ## Table of Contents
7 |
8 | 1. [Overview](#overview)
9 | 2. [Example](#example)
10 | - [Variable definition](#variable-definition)
11 | - [Use data source to fetch a dynamic value](#use-data-source-to-fetch-a-dynamic-value)
12 | - [Build our policy](#build-our-policy)
13 | - [Enrich roles list with created policy](#enrich-roles-list-with-created-policy)
14 | - [Define the iam module](#define-the-iam-module)
15 | 5. [Usage](#usage)
16 | 6. [Requirements](#requirements)
17 | 7. [Providers](#providers)
18 | 8. [Inputs](#inputs)
19 | 9. [Outputs](#outputs)
20 |
21 |
22 | ## Overview
23 |
24 | By using [`aws_iam_policy_document`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) you have the advantage to use terraform's variables inside the policy definition and be able to first gather already existing resources and add them to that policy. This makes the process of creating policies as dynamic and flexible as it can get.
25 |
26 |
27 | ## Example
28 |
29 | The following defines two roles, both without any policy attached.
30 |
31 | #### Variable definition
32 |
33 | `terraform.tfvars`
34 | ```hcl
35 | roles = [
36 | {
37 | name = "ROLE-ADMIN"
38 | path = null
39 | desc = null
40 | trust_policy_file = "data/trust-policy-file.json"
41 | permissions_boundary = null
42 | policies = []
43 | inline_policies = []
44 | policy_arns = []
45 | },
46 | {
47 | name = "ROLE-DEV"
48 | path = null
49 | desc = null
50 | trust_policy_file = "data/trust-policy-file.json"
51 | permissions_boundary = null
52 | policies = []
53 | inline_policies = []
54 | policy_arns = []
55 | },
56 | ]
57 | ```
58 |
59 | #### Use data source to fetch a dynamic value
60 |
61 | Now we're going to dynamically fetch the current AWS account id (just for the sake of this example to have something dynamic).
62 |
63 | `main.tf`
64 | ```hcl
65 | data "aws_caller_identity" "current" {}
66 | ```
67 |
68 | #### Build our policy
69 |
70 | We can then use this account id and include it in our policy document.
71 |
72 | `main.tf`
73 | ```hcl
74 | data "aws_iam_policy_document" "s3" {
75 | statement {
76 | sid = "1"
77 |
78 | actions = [
79 | "s3:ListAllMyBuckets",
80 | ]
81 |
82 | resources = [
83 | "arn:aws:s3::${data.aws_caller_identity.current.account_id}:*",
84 | ]
85 | }
86 | }
87 | ```
88 |
89 | Based on the created policy document, we can define our policy.
90 |
91 | `main.tf`
92 | ```hcl
93 | resource "aws_iam_policy" "s3" {
94 | name = "s3-policy"
95 | path = "/custom/"
96 | description = "Custom S3 policy"
97 | policy = data.aws_iam_policy_document.s3.json
98 |
99 | lifecycle {
100 | create_before_destroy = true
101 | }
102 | }
103 | ```
104 |
105 | #### Enrich roles list with created policy
106 |
107 | We can now enrich the roles list and add this policy to `ROLE-ADMIN`.
108 | We do this by creating a local with the exact same structure and use a condition to attach it to the specific role.
109 |
110 | `main.tf`
111 | ```hcl
112 | locals {
113 | # Roles in this list will have the custom policy added to its policy_arns list
114 | roles_enriched = [
115 | for role in var.roles : {
116 | name = role.name
117 | path = role.path
118 | desc = role.desc
119 | trust_policy_file = role.trust_policy_file
120 | permissions_boundary = role.permissions_boundary
121 | policies = role.policies
122 | inline_policies = role.inline_policies
123 | policy_arns = concat(role.policy_arns, [aws_iam_policy.s3.arn])
124 | } if role["name"] == "ROLE-ADMIN"
125 | ]
126 |
127 | # Roles in this list will be left as they were (condition reversed)
128 | roles_default = [
129 | for role in var.roles : {
130 | name = role.name
131 | path = role.path
132 | desc = role.desc
133 | trust_policy_file = role.trust_policy_file
134 | permissions_boundary = role.permissions_boundary
135 | policies = role.policies
136 | inline_policies = role.inline_policies
137 | policy_arns = role.policy_arns
138 | } if role["name"] != "ROLE-ADMIN"
139 | ]
140 |
141 | # Let's merge both created lists
142 | roles = concat(local.roles_enriched, local.roles_default)
143 | }
144 | ```
145 |
146 | #### Define the iam module
147 |
148 | Now we add everything together and use the iam module
149 |
150 | `main.tf`
151 | ```hcl
152 | module "aws_iam" {
153 | source = "github.com/cytopia/terraform-aws-iam?ref=v5.0.4"
154 |
155 | # Note: we're using the local here as input instead
156 | roles = local.roles
157 | }
158 | ```
159 |
160 |
161 | ## Usage
162 |
163 | To run this example you need to execute:
164 |
165 | ```bash
166 | $ terraform init
167 | $ terraform plan
168 | $ terraform apply
169 | ```
170 |
171 | Note that this example may create resources which cost money. Run terraform destroy when you don't need these resources.
172 |
173 |
174 |
175 | ## Requirements
176 |
177 | No requirements.
178 |
179 | ## Providers
180 |
181 | | Name | Version |
182 | |------|---------|
183 | | aws | n/a |
184 |
185 | ## Inputs
186 |
187 | | Name | Description | Type | Default | Required |
188 | |------|-------------|------|---------|:--------:|
189 | | account\_alias | Assign the account alias for the AWS Account. Unmanaged by default. Resource will be created if the string is non-empty. | `string` | `""` | no |
190 | | account\_pass\_policy | Manages Password Policy for the AWS Account. Unmanaged by default. Resource will be created if 'manage' is set to true. | object({
manage = bool # Set to true, to manage the AWS account password policy
allow_users_to_change_password = bool # Allow users to change their own password?
hard_expiry = bool # Users are prevented from setting a new password after their password has expired?
max_password_age = number # Number of days that an user password is valid
minimum_password_length = number # Minimum length to require for user passwords
password_reuse_prevention = number # The number of previous passwords that users are prevented from reusing
require_lowercase_characters = bool # Require lowercase characters for user passwords?
require_numbers = bool # Require numbers for user passwords?
require_symbols = bool # Require symbols for user passwords?
require_uppercase_characters = bool # Require uppercase characters for user passwords?
}) | {
"allow_users_to_change_password": null,
"hard_expiry": null,
"manage": false,
"max_password_age": null,
"minimum_password_length": null,
"password_reuse_prevention": null,
"require_lowercase_characters": null,
"require_numbers": null,
"require_symbols": null,
"require_uppercase_characters": null
} | no |
191 | | providers\_saml | A list of dictionaries defining saml providers. | list(object({
name = string # The name of the provider to create
file = string # Path to XML generated by identity provider that supports SAML 2.0
})) | `[]` | no |
192 | | providers\_oidc | A list of dictionaries defining openid connect providers. | list(object({
url = string # URL of the identity provider. Corresponds to the iss claim
client_id_list = list(string) # List of client IDs (also known as audiences)
thumbprint_list = list(string) # List of server certificate thumbprints.
})) | `[]` | no |
193 | | policies | A list of dictionaries defining all policies. | list(object({
name = string # Name of the policy
path = string # Defaults to 'var.policy_path' if variable is set to null
desc = string # Defaults to 'var.policy_desc' if variable is set to null
file = string # Path to json or json.tmpl file of policy
vars = map(string) # Policy template variables {key: val, ...}
})) | `[]` | no |
194 | | groups | A list of dictionaries defining all groups. | list(object({
name = string # Name of the group
path = string # Defaults to 'var.group_path' if variable is set to null
policies = list(string) # List of names of policies (must be defined in var.policies)
policy_arns = list(string) # List of existing policy ARN's
inline_policies = list(object({
name = string # Name of the inline policy
file = string # Path to json or json.tmpl file of policy
vars = map(string) # Policy template variables {key = val, ...}
}))
})) | `[]` | no |
195 | | users | A list of dictionaries defining all users. | list(object({
name = string # Name of the user
path = string # Defaults to 'var.user_path' if variable is set to null
groups = list(string) # List of group names to add this user to
access_keys = list(object({
name = string # IaC identifier for first or second IAM access key (not used on AWS)
pgp_key = string # Leave empty for non or provide a b64-enc pubkey or keybase username
status = string # 'Active' or 'Inactive'
}))
permissions_boundary = string # ARN to a policy used as permissions boundary (or null/empty)
policies = list(string) # List of names of policies (must be defined in var.policies)
policy_arns = list(string) # List of existing policy ARN's
inline_policies = list(object({
name = string # Name of the inline policy
file = string # Path to json or json.tmpl file of policy
vars = map(string) # Policy template variables {key = val, ...}
}))
})) | `[]` | no |
196 | | roles | A list of dictionaries defining all roles. | list(object({
name = string # Name of the role
path = string # Defaults to 'var.role_path' if variable is set to null
desc = string # Defaults to 'var.role_desc' if variable is set to null
trust_policy_file = string # Path to file of trust/assume policy
permissions_boundary = string # ARN to a policy used as permissions boundary (or null/empty)
policies = list(string) # List of names of policies (must be defined in var.policies)
policy_arns = list(string) # List of existing policy ARN's
inline_policies = list(object({
name = string # Name of the inline policy
file = string # Path to json or json.tmpl file of policy
vars = map(string) # Policy template variables {key = val, ...}
}))
})) | `[]` | no |
197 | | policy\_path | The default path under which to create the policy if not specified in the policies list. You can use a single path, or nest multiple paths as if they were a folder structure. For example, you could use the nested path /division\_abc/subdivision\_xyz/product\_1234/engineering/ to match your company's organizational structure. | `string` | `"/"` | no |
198 | | policy\_desc | The default description of the policy. | `string` | `"Managed by Terraform"` | no |
199 | | group\_path | The path under which to create the group. You can use a single path, or nest multiple paths as if they were a folder structure. For example, you could use the nested path /division\_abc/subdivision\_xyz/product\_1234/engineering/ to match your company's organizational structure. | `string` | `"/"` | no |
200 | | user\_path | The path under which to create the user. You can use a single path, or nest multiple paths as if they were a folder structure. For example, you could use the nested path /division\_abc/subdivision\_xyz/product\_1234/engineering/ to match your company's organizational structure. | `string` | `"/"` | no |
201 | | role\_path | The path under which to create the role. You can use a single path, or nest multiple paths as if they were a folder structure. For example, you could use the nested path /division\_abc/subdivision\_xyz/product\_1234/engineering/ to match your company's organizational structure. | `string` | `"/"` | no |
202 | | role\_desc | The description of the role. | `string` | `"Managed by Terraform"` | no |
203 | | role\_max\_session\_duration | The maximum session duration (in seconds) that you want to set for the specified role. This setting can have a value from 1 hour to 12 hours specified in seconds. | `string` | `"3600"` | no |
204 | | role\_force\_detach\_policies | Specifies to force detaching any policies the role has before destroying it. | `bool` | `true` | no |
205 | | tags | Key-value mapping of tags for the IAM role or user. | `map(any)` | `{}` | no |
206 |
207 | ## Outputs
208 |
209 | | Name | Description |
210 | |------|-------------|
211 | | roles | Created roles |
212 |
213 |
214 |
--------------------------------------------------------------------------------
/variables.tf:
--------------------------------------------------------------------------------
1 | # -------------------------------------------------------------------------------------------------
2 | # Account setting transformations
3 | # -------------------------------------------------------------------------------------------------
4 |
5 | variable "account_alias" {
6 | description = "Assign the account alias for the AWS Account. Unmanaged by default. Resource will be created if the string is non-empty."
7 | type = string
8 | default = ""
9 | }
10 |
11 | variable "account_pass_policy" {
12 | description = "Manages Password Policy for the AWS Account. Unmanaged by default. Resource will be created if 'manage' is set to true."
13 | type = object({
14 | manage = bool # Set to true, to manage the AWS account password policy
15 | allow_users_to_change_password = bool # Allow users to change their own password?
16 | hard_expiry = bool # Users are prevented from setting a new password after their password has expired?
17 | max_password_age = number # Number of days that an user password is valid
18 | minimum_password_length = number # Minimum length to require for user passwords
19 | password_reuse_prevention = number # The number of previous passwords that users are prevented from reusing
20 | require_lowercase_characters = bool # Require lowercase characters for user passwords?
21 | require_numbers = bool # Require numbers for user passwords?
22 | require_symbols = bool # Require symbols for user passwords?
23 | require_uppercase_characters = bool # Require uppercase characters for user passwords?
24 | })
25 | default = {
26 | manage = false
27 | allow_users_to_change_password = null
28 | hard_expiry = null
29 | max_password_age = null
30 | minimum_password_length = null
31 | password_reuse_prevention = null
32 | require_lowercase_characters = null
33 | require_numbers = null
34 | require_symbols = null
35 | require_uppercase_characters = null
36 | }
37 | }
38 |
39 |
40 | # -------------------------------------------------------------------------------------------------
41 | # Identity Providers
42 | # -------------------------------------------------------------------------------------------------
43 |
44 | variable "providers_saml" {
45 | description = "A list of dictionaries defining saml providers."
46 | type = list(object({
47 | name = string # The name of the provider to create
48 | file = string # Path to XML generated by identity provider that supports SAML 2.0
49 | }))
50 | default = []
51 | }
52 |
53 | variable "providers_oidc" {
54 | description = "A list of dictionaries defining openid connect providers."
55 | type = list(object({
56 | url = string # URL of the identity provider. Corresponds to the iss claim
57 | client_id_list = list(string) # List of client IDs (also known as audiences)
58 | thumbprint_list = list(string) # List of server certificate thumbprints.
59 | }))
60 | default = []
61 | }
62 |
63 |
64 | # -------------------------------------------------------------------------------------------------
65 | # Policy definition
66 | # -------------------------------------------------------------------------------------------------
67 |
68 | # Example policy definition:
69 | #
70 | # policies = [
71 | # {
72 | # name = "default-permission-boundary"
73 | # path = "/boundaries/human/"
74 | # desc = "Provides default permission boundary for assume roles"
75 | # file = "boundaries/default.json.tmpl"
76 | # vars = {
77 | # currencryDescripe = "*",
78 | # }
79 | # },
80 | # {
81 | # name = "assume-human-ro-billing"
82 | # path = "/assume/human/"
83 | # desc = "Provides read-only access to billing"
84 | # file = "policies/human/ro-billing.json"
85 | # vars = {}
86 | # },
87 | # {
88 | # name = "sqs-ro"
89 | # path = "/custom/human/"
90 | # desc = "Provides read-only access to SQS"
91 | # file = "policies/human/sqs-ro.json"
92 | # vars = {}
93 | # },
94 | # ]
95 | variable "policies" {
96 | description = "A list of dictionaries defining all policies."
97 | type = list(object({
98 | name = string # Name of the policy
99 | path = string # Defaults to 'var.policy_path' if variable is set to null
100 | desc = string # Defaults to 'var.policy_desc' if variable is set to null
101 | file = string # Path to json or json.tmpl file of policy
102 | vars = map(string) # Policy template variables {key: val, ...}
103 | }))
104 | default = []
105 | }
106 |
107 |
108 | # -------------------------------------------------------------------------------------------------
109 | # Group definition
110 | # -------------------------------------------------------------------------------------------------
111 |
112 | variable "groups" {
113 | description = "A list of dictionaries defining all groups."
114 | type = list(object({
115 | name = string # Name of the group
116 | path = string # Defaults to 'var.group_path' if variable is set to null
117 | policies = list(string) # List of names of policies (must be defined in var.policies)
118 | policy_arns = list(string) # List of existing policy ARN's
119 | inline_policies = list(object({
120 | name = string # Name of the inline policy
121 | file = string # Path to json or json.tmpl file of policy
122 | vars = map(string) # Policy template variables {key = val, ...}
123 | }))
124 | }))
125 | default = []
126 | }
127 |
128 |
129 | # -------------------------------------------------------------------------------------------------
130 | # User definition
131 | # -------------------------------------------------------------------------------------------------
132 |
133 | # Example user definition:
134 | #
135 | # users = [
136 | # {
137 | # name = "ADMIN-USER"
138 | # path = ""
139 | # groups = []
140 | # access_keys = [
141 | # {
142 | # name = "key1"
143 | # pgp_key = ""
144 | # status = ""
145 | # },
146 | # {
147 | # name = "key2"
148 | # pgp_key = ""
149 | # status = ""
150 | # }
151 | # ]
152 | # permissions_boundary = null
153 | # policies = []
154 | # policy_arns = [
155 | # "arn:aws:iam::aws:policy/AdministratorAccess",
156 | # ]
157 | # inline_policies = []
158 | # },
159 | # {
160 | # name = "POWER-USER"
161 | # path = ""
162 | # groups = [
163 | # "groupname1",
164 | # "groupname2",
165 | # ]
166 | # access_keys = []
167 | # permissions_boundary = "arn:aws:iam::aws:policy/my-boundary"
168 | # policies = [
169 | # "assume-human-ro-billing",
170 | # ]
171 | # policy_arns = [
172 | # "arn:aws:iam::aws:policy/PowerUserAccess",
173 | # ]
174 | # inline_policies = []
175 | # },
176 | # ]
177 | variable "users" {
178 | description = "A list of dictionaries defining all users."
179 | type = list(object({
180 | name = string # Name of the user
181 | path = string # Defaults to 'var.user_path' if variable is set to null
182 | groups = list(string) # List of group names to add this user to
183 | access_keys = list(object({
184 | name = string # IaC identifier for first or second IAM access key (not used on AWS)
185 | pgp_key = string # Leave empty for non or provide a b64-enc pubkey or keybase username
186 | status = string # 'Active' or 'Inactive'
187 | }))
188 | permissions_boundary = string # ARN to a policy used as permissions boundary (or null/empty)
189 | policies = list(string) # List of names of policies (must be defined in var.policies)
190 | policy_arns = list(string) # List of existing policy ARN's
191 | inline_policies = list(object({
192 | name = string # Name of the inline policy
193 | file = string # Path to json or json.tmpl file of policy
194 | vars = map(string) # Policy template variables {key = val, ...}
195 | }))
196 | }))
197 | default = []
198 | }
199 |
200 |
201 | # -------------------------------------------------------------------------------------------------
202 | # Role definition
203 | # -------------------------------------------------------------------------------------------------
204 |
205 | # Example role definition:
206 | #
207 | # roles = [
208 | # {
209 | # name = "ASSUME-ADMIN"
210 | # path = ""
211 | # desc = "Description"
212 | # trust_policy_file = "trust-policies/eng-ops.json"
213 | # permissions_boundary = null
214 | # policies = []
215 | # policy_arns = ["arn:aws:iam::aws:policy/AdministratorAccess"],
216 | # inline_policies = []
217 | # },
218 | # {
219 | # name = "ASSUME-DEV"
220 | # path = null
221 | # desc = null
222 | # trust_policy_file = "trust-policies/eng-dev.json"
223 | # permissions_boundary = "arn:aws:iam::aws:policy/my-boundary"
224 | # policies = [
225 | # "assume-human-ro-billing",
226 | # ]
227 | # policy_arns = [
228 | # "arn:aws:iam::aws:policy/PowerUserAccess",
229 | # ]
230 | # inline_policies = [
231 | # {
232 | # name = "mypolicy"
233 | # file = "data/policy.json"
234 | # vars = {}
235 | # }
236 | # ]
237 | # },
238 | # ]
239 | variable "roles" {
240 | description = "A list of dictionaries defining all roles."
241 | type = list(object({
242 | name = string # Name of the role
243 | path = string # Defaults to 'var.role_path' if variable is set to null
244 | desc = string # Defaults to 'var.role_desc' if variable is set to null
245 | trust_policy_file = string # Path to file of trust/assume policy
246 | permissions_boundary = string # ARN to a policy used as permissions boundary (or null/empty)
247 | policies = list(string) # List of names of policies (must be defined in var.policies)
248 | policy_arns = list(string) # List of existing policy ARN's
249 | inline_policies = list(object({
250 | name = string # Name of the inline policy
251 | file = string # Path to json or json.tmpl file of policy
252 | vars = map(string) # Policy template variables {key = val, ...}
253 | }))
254 | }))
255 | default = []
256 | }
257 |
258 |
259 | # -------------------------------------------------------------------------------------------------
260 | # Default Policy settings
261 | # -------------------------------------------------------------------------------------------------
262 |
263 | variable "policy_path" {
264 | description = "The default path under which to create the policy if not specified in the policies list. You can use a single path, or nest multiple paths as if they were a folder structure. For example, you could use the nested path /division_abc/subdivision_xyz/product_1234/engineering/ to match your company's organizational structure."
265 | default = "/"
266 | }
267 |
268 | variable "policy_desc" {
269 | description = "The default description of the policy."
270 | default = "Managed by Terraform"
271 | }
272 |
273 |
274 | # -------------------------------------------------------------------------------------------------
275 | # Default Group settings
276 | # -------------------------------------------------------------------------------------------------
277 |
278 | variable "group_path" {
279 | description = "The path under which to create the group. You can use a single path, or nest multiple paths as if they were a folder structure. For example, you could use the nested path /division_abc/subdivision_xyz/product_1234/engineering/ to match your company's organizational structure."
280 | default = "/"
281 | }
282 |
283 |
284 | # -------------------------------------------------------------------------------------------------
285 | # Default User settings
286 | # -------------------------------------------------------------------------------------------------
287 |
288 | variable "user_path" {
289 | description = "The path under which to create the user. You can use a single path, or nest multiple paths as if they were a folder structure. For example, you could use the nested path /division_abc/subdivision_xyz/product_1234/engineering/ to match your company's organizational structure."
290 | default = "/"
291 | }
292 |
293 |
294 | # -------------------------------------------------------------------------------------------------
295 | # Default Role settings
296 | # -------------------------------------------------------------------------------------------------
297 |
298 | variable "role_path" {
299 | description = "The path under which to create the role. You can use a single path, or nest multiple paths as if they were a folder structure. For example, you could use the nested path /division_abc/subdivision_xyz/product_1234/engineering/ to match your company's organizational structure."
300 | default = "/"
301 | }
302 |
303 | variable "role_desc" {
304 | description = "The description of the role."
305 | default = "Managed by Terraform"
306 | }
307 |
308 | variable "role_max_session_duration" {
309 | description = "The maximum session duration (in seconds) that you want to set for the specified role. This setting can have a value from 1 hour to 12 hours specified in seconds."
310 | default = "3600"
311 | }
312 |
313 | variable "role_force_detach_policies" {
314 | description = "Specifies to force detaching any policies the role has before destroying it."
315 | default = true
316 | }
317 |
318 |
319 | # -------------------------------------------------------------------------------------------------
320 | # Default general settings
321 | # -------------------------------------------------------------------------------------------------
322 |
323 | variable "tags" {
324 | description = "Key-value mapping of tags for the IAM role or user."
325 | type = map(any)
326 | default = {}
327 | }
328 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Terraform module: AWS IAM
2 |
3 | **[Features](#star-features)** |
4 | **[Important](#exclamation-important)** |
5 | **[Examples](#bulb-examples)** |
6 | **[Usage](#computer-usage)** |
7 | **[Inputs](#required-inputs)** |
8 | **[Outputs](#outputs)** |
9 | **[Related projects](#related-projects)** |
10 | **[Authors](#authors)** |
11 | **[License](#license)**
12 |
13 | [](https://github.com/cytopia/terraform-aws-iam/actions?query=workflow%3Alint)
14 | [](https://github.com/cytopia/terraform-aws-iam/actions?query=workflow%3Atest)
15 | [](https://github.com/cytopia/terraform-aws-iam/releases)
16 | [](https://registry.terraform.io/modules/cytopia/iam/aws/)
17 | [](https://opensource.org/licenses/MIT)
18 |
19 |
20 | This Terraform module manages AWS IAM to its full extend.
21 |
22 | It is only required to have a single module invocation per AWS account, as this module allows the creation of unlimited resources and you will therefore have an auditable single source of truth for IAM.
23 |
24 |
25 | ## :star: Features
26 |
27 | * Completely configurable via `terraform.tfvars` only
28 | * Arbitrary number of IAM **policies**, **groups**, **users** and **roles**
29 | * Policies can be defined via **JSON** or **templatable JSON** files
30 | * Policies can be defined via [`aws_iam_policy_document`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) ([Example here](examples/policies-with-custom-data-sources))
31 | * Groups, users and roles can be attached to an arbitrary number of **custom policies**, **inline policies** and existing **policy ARN's**
32 | * Users can be added to an arbitrary number of **groups**
33 | * Users support AWS access/secret **[key rotation](examples/access-key-rotation/)**
34 | * Roles support **trusted entities**
35 | * Arbitrary number of **identity providers** (SAML and OIDC)
36 | * **Account settings**: account alias and password policy
37 |
38 |
39 | ## :exclamation: Important
40 |
41 | When creating an IAM user with an `Inactive` access key, it is initially created with access key set to `Active`. You will have to run it a second time in order to deactivate the access key.
42 | This is either an issue with the terraform resource [`aws_iam_access_key`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_access_key) or with the AWS api itself.
43 |
44 |
45 | ## :bulb: Examples
46 |
47 | This module is very flexible and might look a bit complicated at first glance. To show off a few features which are possible, have a look at the following examples.
48 |
49 | **:page_facing_up: Also see each example README.md file for more detailed explanations on each of the covered resources. They serve as a documentation purpose as well.**
50 |
51 | | Example | Description |
52 | |-------------------------------------------------------------------|----------------------------------------------------------|
53 | | **POLICIES** | |
54 | | [JSON policies](examples/policies/) | Define JSON policies with variable templating |
55 | | [Policies with custom data sources](examples/policies-with-custom-data-sources) | Use terraform's [`aws_iam_policy_document`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) data source to create policies and attach them to defined roles. |
56 | | **GROUPS / USERS** | |
57 | | [Groups](examples/groups/) | Defines groups |
58 | | [Users](examples/users/) | Defines users |
59 | | [Groups, users and policies](examples/groups-users-and-policies/) | Defines groups, users and policies |
60 | | [Access key rotation](examples/access-key-rotation/) | Shows how to safely rotate AWS access keys for IAM users |
61 | | **ROLES** | |
62 | | [Roles](examples/roles/) | Define roles (cross-account assumable) |
63 | | **ADVANCED** | |
64 | | [SAML Login](examples/saml-login/) | Login into AWS via SAML identity provider and assume cross-account roles. Also read about best-practices for separating login roles and permission roles. |
65 |
66 |
67 | ## :computer: Usage
68 |
69 | 1. [Use `terraform.tfvars` only](#use-terraformtfvars-only)
70 | 2. [Use Module](#use-module)
71 | 3. [Use Terragrunt](#use-terragrunt)
72 |
73 | ### Use `terraform.tfvars` only
74 |
75 | You can simply clone this repository and add your `terraform.tfvars` file into the root of this directory.
76 |
77 | `terraform.tfvars`
78 | ```hcl
79 | # --------------------------------------------------------------------------------
80 | # Account Management
81 | # --------------------------------------------------------------------------------
82 |
83 | account_alias = "prod-account"
84 |
85 | account_pass_policy = {
86 | manage = true
87 | allow_users_to_change_password = true
88 | hard_expiry = false
89 | max_password_age = 365
90 | minimum_password_length = 8
91 | password_reuse_prevention = 5
92 | require_lowercase_characters = true
93 | require_numbers = true
94 | require_symbols = true
95 | require_uppercase_characters = true
96 | }
97 |
98 | # --------------------------------------------------------------------------------
99 | # Account Identity provider
100 | # --------------------------------------------------------------------------------
101 |
102 | # Add a SAML provider for login
103 | providers_saml = [
104 | {
105 | name = "AzureAD"
106 | file = "path/to/azure/meta.xml"
107 | },
108 | {
109 | name = "ADFS"
110 | file = "path/to/adfs/meta.xml"
111 | }
112 | ]
113 |
114 | # --------------------------------------------------------------------------------
115 | # Policies, Groups, Users and Roles
116 | # --------------------------------------------------------------------------------
117 |
118 | # List of policies to create
119 | # Policies defined here can be used by name in groups, users and roles list
120 | policies = [
121 | {
122 | name = "ro-billing"
123 | path = "/assume/human/"
124 | desc = "Provides read-only access to billing"
125 | file = "policies/ro-billing.json"
126 | vars = {}
127 | },
128 | ]
129 |
130 | # List of groups to manage
131 | # Groups defined here can be used in users list
132 | groups = [
133 | {
134 | name = "admin-group"
135 | path = null
136 | policies = []
137 | policy_arns = [
138 | "arn:aws:iam::aws:policy/AdministratorAccess",
139 | ]
140 | inline_policies = []
141 | },
142 | ]
143 |
144 | # List of users to manage
145 | users = [
146 | {
147 | name = "admin"
148 | path = null
149 | groups = ["admin-group"]
150 | access_keys = []
151 | permissions_boundary = null
152 | policies = []
153 | policy_arns = []
154 | inline_policies = []
155 | },
156 | ]
157 |
158 | # List of roles to manage
159 | roles = [
160 | {
161 | name = "ROLE-ADMIN"
162 | path = ""
163 | desc = ""
164 | trust_policy_file = "trust-policies/admin.json"
165 | permissions_boundary = null
166 | policies = []
167 | policy_arns = [
168 | "arn:aws:iam::aws:policy/AdministratorAccess",
169 | ]
170 | inline_policies = []
171 | },
172 | {
173 | name = "ROLE-DEV"
174 | path = ""
175 | desc = ""
176 | trust_policy_file = "trust-policies/dev.json"
177 | permissions_boundary = "arn:aws:iam::aws:policy/PowerUserAccess"
178 | policies = [
179 | "ro-billing",
180 | ]
181 | policy_arns = [
182 | "arn:aws:iam::aws:policy/PowerUserAccess",
183 | ]
184 | inline_policies = []
185 | },
186 | ]
187 |
188 | # --------------------------------------------------------------------------------
189 | # Defaults
190 | # --------------------------------------------------------------------------------
191 |
192 | policy_path = "/"
193 | policy_desc = "Managed by Terraform"
194 | group_path = "/"
195 | user_path = "/"
196 | role_path = "/"
197 | role_desc = "Managed by Terraform"
198 |
199 | role_max_session_duration = 3600
200 | role_force_detach_policies = true
201 |
202 | tags = {
203 | env = "prod"
204 | owner = "terraform"
205 | }
206 | ```
207 |
208 |
209 | ### Use Module
210 |
211 | Create your own module by sourcing this module.
212 |
213 | ```hcl
214 | module "iam_roles" {
215 | source = "github.com/cytopia/terraform-aws-iam?ref=v5.0.5"
216 |
217 | # --------------------------------------------------------------------------------
218 | # Account Management
219 | # --------------------------------------------------------------------------------
220 |
221 | account_alias = "prod-account"
222 |
223 | account_pass_policy = {
224 | manage = true
225 | allow_users_to_change_password = true
226 | hard_expiry = false
227 | max_password_age = 365
228 | minimum_password_length = 8
229 | password_reuse_prevention = 5
230 | require_lowercase_characters = true
231 | require_numbers = true
232 | require_symbols = true
233 | require_uppercase_characters = true
234 | }
235 |
236 | # --------------------------------------------------------------------------------
237 | # Account Identity provider
238 | # --------------------------------------------------------------------------------
239 |
240 | # Add a SAML provider for login
241 | providers_saml = [
242 | {
243 | name = "AzureAD"
244 | file = "path/to/azure/meta.xml"
245 | },
246 | {
247 | name = "ADFS"
248 | file = "path/to/adfs/meta.xml"
249 | }
250 | ]
251 |
252 | # --------------------------------------------------------------------------------
253 | # Policies, Groups, Users and Roles
254 | # --------------------------------------------------------------------------------
255 |
256 | # List of policies to create
257 | # Policies defined here can be used by name in groups, users and roles list
258 | policies = [
259 | {
260 | name = "ro-billing"
261 | path = "/assume/human/"
262 | desc = "Provides read-only access to billing"
263 | file = "policies/ro-billing.json"
264 | vars = {}
265 | },
266 | ]
267 |
268 | # List of groups to manage
269 | # Groups defined here can be used in users list
270 | groups = [
271 | {
272 | name = "admin-group"
273 | path = null
274 | policies = []
275 | policy_arns = [
276 | "arn:aws:iam::aws:policy/AdministratorAccess",
277 | ]
278 | inline_policies = []
279 | },
280 | ]
281 |
282 | # List of users to manage
283 | users = [
284 | {
285 | name = "admin"
286 | path = null
287 | groups = ["admin-group"]
288 | access_keys = []
289 | permissions_boundary = null
290 | policies = []
291 | policy_arns = []
292 | inline_policies = []
293 | },
294 | ]
295 |
296 | # List of roles to manage
297 | roles = [
298 | {
299 | name = "ROLE-ADMIN"
300 | path = ""
301 | desc = ""
302 | trust_policy_file = "trust-policies/admin.json"
303 | permissions_boundary = null
304 | policies = []
305 | policy_arns = [
306 | "arn:aws:iam::aws:policy/AdministratorAccess",
307 | ]
308 | inline_policies = []
309 | },
310 | {
311 | name = "ROLE-DEV"
312 | path = ""
313 | desc = ""
314 | trust_policy_file = "trust-policies/dev.json"
315 | permissions_boundary = "arn:aws:iam::aws:policy/PowerUserAccess"
316 | policies = [
317 | "ro-billing",
318 | ]
319 | policy_arns = [
320 | "arn:aws:iam::aws:policy/PowerUserAccess",
321 | ]
322 | inline_policies = []
323 | },
324 | ]
325 |
326 | # --------------------------------------------------------------------------------
327 | # Defaults
328 | # --------------------------------------------------------------------------------
329 |
330 | policy_path = "/"
331 | policy_desc = "Managed by Terraform"
332 | group_path = "/"
333 | user_path = "/"
334 | role_path = "/"
335 | role_desc = "Managed by Terraform"
336 |
337 | role_max_session_duration = 3600
338 | role_force_detach_policies = true
339 |
340 | tags = {
341 | env = "prod"
342 | owner = "terraform"
343 | }
344 | }
345 | ```
346 |
347 | ### Use Terragrunt
348 |
349 | Wrap this module into Terragrunt
350 |
351 | ```hcl
352 | terraform {
353 | source = "github.com/cytopia/terraform-aws-iam?ref=v5.0.5"
354 | }
355 |
356 | inputs = {
357 | # --------------------------------------------------------------------------------
358 | # Account Management
359 | # --------------------------------------------------------------------------------
360 |
361 | account_alias = "prod-account"
362 |
363 | account_pass_policy = {
364 | manage = true
365 | allow_users_to_change_password = true
366 | hard_expiry = false
367 | max_password_age = 365
368 | minimum_password_length = 8
369 | password_reuse_prevention = 5
370 | require_lowercase_characters = true
371 | require_numbers = true
372 | require_symbols = true
373 | require_uppercase_characters = true
374 | }
375 |
376 | # --------------------------------------------------------------------------------
377 | # Account Identity provider
378 | # --------------------------------------------------------------------------------
379 |
380 | # Add a SAML providers for login
381 | providers_saml = [
382 | {
383 | name = "AzureAD"
384 | file = "path/to/azure/meta.xml"
385 | },
386 | {
387 | name = "ADFS"
388 | file = "path/to/adfs/meta.xml"
389 | }
390 | ]
391 |
392 | # --------------------------------------------------------------------------------
393 | # Policies, Groups, Users and Roles
394 | # --------------------------------------------------------------------------------
395 |
396 | # List of policies to create
397 | # Policies defined here can be used by name in groups, users and roles list
398 | policies = [
399 | {
400 | name = "ro-billing"
401 | path = "/assume/human/"
402 | desc = "Provides read-only access to billing"
403 | file = "policies/ro-billing.json"
404 | vars = {}
405 | },
406 | ]
407 |
408 | # List of groups to manage
409 | # Groups defined here can be used in users list
410 | groups = [
411 | {
412 | name = "admin-group"
413 | path = null
414 | policies = []
415 | policy_arns = [
416 | "arn:aws:iam::aws:policy/AdministratorAccess",
417 | ]
418 | inline_policies = []
419 | },
420 | ]
421 |
422 | # List of users to manage
423 | users = [
424 | {
425 | name = "admin"
426 | path = null
427 | groups = ["admin-group"]
428 | access_keys = []
429 | permissions_boundary = null
430 | policies = []
431 | policy_arns = []
432 | inline_policies = []
433 | },
434 | ]
435 |
436 | # List of roles to manage
437 | roles = [
438 | {
439 | name = "ROLE-ADMIN"
440 | path = ""
441 | desc = ""
442 | trust_policy_file = "trust-policies/admin.json"
443 | permissions_boundary = null
444 | policies = []
445 | policy_arns = [
446 | "arn:aws:iam::aws:policy/AdministratorAccess",
447 | ]
448 | inline_policies = []
449 | },
450 | {
451 | name = "ROLE-DEV"
452 | path = ""
453 | desc = ""
454 | trust_policy_file = "trust-policies/dev.json"
455 | permissions_boundary = "arn:aws:iam::aws:policy/PowerUserAccess"
456 | policies = [
457 | "ro-billing",
458 | ]
459 | policy_arns = [
460 | "arn:aws:iam::aws:policy/PowerUserAccess",
461 | ]
462 | inline_policies = []
463 | },
464 | ]
465 |
466 | # --------------------------------------------------------------------------------
467 | # Defaults
468 | # --------------------------------------------------------------------------------
469 |
470 | policy_path = "/"
471 | policy_desc = "Managed by Terraform"
472 | group_path = "/"
473 | user_path = "/"
474 | role_path = "/"
475 | role_desc = "Managed by Terraform"
476 |
477 | role_max_session_duration = 3600
478 | role_force_detach_policies = true
479 |
480 | tags = {
481 | env = "prod"
482 | owner = "terraform"
483 | }
484 | }
485 | ```
486 |
487 |
488 |
489 | ## Required Inputs
490 |
491 | No required input.
492 |
493 | ## Optional Inputs
494 |
495 | The following input variables are optional (have default values):
496 |
497 | ### account\_alias
498 |
499 | Description: Assign the account alias for the AWS Account. Unmanaged by default. Resource will be created if the string is non-empty.
500 |
501 | Type: `string`
502 |
503 | Default: `""`
504 |
505 | ### account\_pass\_policy
506 |
507 | Description: Manages Password Policy for the AWS Account. Unmanaged by default. Resource will be created if 'manage' is set to true.
508 |
509 | Type:
510 |
511 | ```hcl
512 | object({
513 | manage = bool # Set to true, to manage the AWS account password policy
514 | allow_users_to_change_password = bool # Allow users to change their own password?
515 | hard_expiry = bool # Users are prevented from setting a new password after their password has expired?
516 | max_password_age = number # Number of days that an user password is valid
517 | minimum_password_length = number # Minimum length to require for user passwords
518 | password_reuse_prevention = number # The number of previous passwords that users are prevented from reusing
519 | require_lowercase_characters = bool # Require lowercase characters for user passwords?
520 | require_numbers = bool # Require numbers for user passwords?
521 | require_symbols = bool # Require symbols for user passwords?
522 | require_uppercase_characters = bool # Require uppercase characters for user passwords?
523 | })
524 | ```
525 |
526 | Default:
527 |
528 | ```json
529 | {
530 | "allow_users_to_change_password": null,
531 | "hard_expiry": null,
532 | "manage": false,
533 | "max_password_age": null,
534 | "minimum_password_length": null,
535 | "password_reuse_prevention": null,
536 | "require_lowercase_characters": null,
537 | "require_numbers": null,
538 | "require_symbols": null,
539 | "require_uppercase_characters": null
540 | }
541 | ```
542 |
543 | ### providers\_saml
544 |
545 | Description: A list of dictionaries defining saml providers.
546 |
547 | Type:
548 |
549 | ```hcl
550 | list(object({
551 | name = string # The name of the provider to create
552 | file = string # Path to XML generated by identity provider that supports SAML 2.0
553 | }))
554 | ```
555 |
556 | Default: `[]`
557 |
558 | ### providers\_oidc
559 |
560 | Description: A list of dictionaries defining openid connect providers.
561 |
562 | Type:
563 |
564 | ```hcl
565 | list(object({
566 | url = string # URL of the identity provider. Corresponds to the iss claim
567 | client_id_list = list(string) # List of client IDs (also known as audiences)
568 | thumbprint_list = list(string) # List of server certificate thumbprints.
569 | }))
570 | ```
571 |
572 | Default: `[]`
573 |
574 | ### policies
575 |
576 | Description: A list of dictionaries defining all policies.
577 |
578 | Type:
579 |
580 | ```hcl
581 | list(object({
582 | name = string # Name of the policy
583 | path = string # Defaults to 'var.policy_path' if variable is set to null
584 | desc = string # Defaults to 'var.policy_desc' if variable is set to null
585 | file = string # Path to json or json.tmpl file of policy
586 | vars = map(string) # Policy template variables {key: val, ...}
587 | }))
588 | ```
589 |
590 | Default: `[]`
591 |
592 | ### groups
593 |
594 | Description: A list of dictionaries defining all groups.
595 |
596 | Type:
597 |
598 | ```hcl
599 | list(object({
600 | name = string # Name of the group
601 | path = string # Defaults to 'var.group_path' if variable is set to null
602 | policies = list(string) # List of names of policies (must be defined in var.policies)
603 | policy_arns = list(string) # List of existing policy ARN's
604 | inline_policies = list(object({
605 | name = string # Name of the inline policy
606 | file = string # Path to json or json.tmpl file of policy
607 | vars = map(string) # Policy template variables {key = val, ...}
608 | }))
609 | }))
610 | ```
611 |
612 | Default: `[]`
613 |
614 | ### users
615 |
616 | Description: A list of dictionaries defining all users.
617 |
618 | Type:
619 |
620 | ```hcl
621 | list(object({
622 | name = string # Name of the user
623 | path = string # Defaults to 'var.user_path' if variable is set to null
624 | groups = list(string) # List of group names to add this user to
625 | access_keys = list(object({
626 | name = string # IaC identifier for first or second IAM access key (not used on AWS)
627 | pgp_key = string # Leave empty for non or provide a b64-enc pubkey or keybase username
628 | status = string # 'Active' or 'Inactive'
629 | }))
630 | permissions_boundary = string # ARN to a policy used as permissions boundary (or null/empty)
631 | policies = list(string) # List of names of policies (must be defined in var.policies)
632 | policy_arns = list(string) # List of existing policy ARN's
633 | inline_policies = list(object({
634 | name = string # Name of the inline policy
635 | file = string # Path to json or json.tmpl file of policy
636 | vars = map(string) # Policy template variables {key = val, ...}
637 | }))
638 | }))
639 | ```
640 |
641 | Default: `[]`
642 |
643 | ### roles
644 |
645 | Description: A list of dictionaries defining all roles.
646 |
647 | Type:
648 |
649 | ```hcl
650 | list(object({
651 | name = string # Name of the role
652 | path = string # Defaults to 'var.role_path' if variable is set to null
653 | desc = string # Defaults to 'var.role_desc' if variable is set to null
654 | trust_policy_file = string # Path to file of trust/assume policy
655 | permissions_boundary = string # ARN to a policy used as permissions boundary (or null/empty)
656 | policies = list(string) # List of names of policies (must be defined in var.policies)
657 | policy_arns = list(string) # List of existing policy ARN's
658 | inline_policies = list(object({
659 | name = string # Name of the inline policy
660 | file = string # Path to json or json.tmpl file of policy
661 | vars = map(string) # Policy template variables {key = val, ...}
662 | }))
663 | }))
664 | ```
665 |
666 | Default: `[]`
667 |
668 | ### policy\_path
669 |
670 | Description: The default path under which to create the policy if not specified in the policies list. You can use a single path, or nest multiple paths as if they were a folder structure. For example, you could use the nested path /division\_abc/subdivision\_xyz/product\_1234/engineering/ to match your company's organizational structure.
671 |
672 | Type: `string`
673 |
674 | Default: `"/"`
675 |
676 | ### policy\_desc
677 |
678 | Description: The default description of the policy.
679 |
680 | Type: `string`
681 |
682 | Default: `"Managed by Terraform"`
683 |
684 | ### group\_path
685 |
686 | Description: The path under which to create the group. You can use a single path, or nest multiple paths as if they were a folder structure. For example, you could use the nested path /division\_abc/subdivision\_xyz/product\_1234/engineering/ to match your company's organizational structure.
687 |
688 | Type: `string`
689 |
690 | Default: `"/"`
691 |
692 | ### user\_path
693 |
694 | Description: The path under which to create the user. You can use a single path, or nest multiple paths as if they were a folder structure. For example, you could use the nested path /division\_abc/subdivision\_xyz/product\_1234/engineering/ to match your company's organizational structure.
695 |
696 | Type: `string`
697 |
698 | Default: `"/"`
699 |
700 | ### role\_path
701 |
702 | Description: The path under which to create the role. You can use a single path, or nest multiple paths as if they were a folder structure. For example, you could use the nested path /division\_abc/subdivision\_xyz/product\_1234/engineering/ to match your company's organizational structure.
703 |
704 | Type: `string`
705 |
706 | Default: `"/"`
707 |
708 | ### role\_desc
709 |
710 | Description: The description of the role.
711 |
712 | Type: `string`
713 |
714 | Default: `"Managed by Terraform"`
715 |
716 | ### role\_max\_session\_duration
717 |
718 | Description: The maximum session duration (in seconds) that you want to set for the specified role. This setting can have a value from 1 hour to 12 hours specified in seconds.
719 |
720 | Type: `string`
721 |
722 | Default: `"3600"`
723 |
724 | ### role\_force\_detach\_policies
725 |
726 | Description: Specifies to force detaching any policies the role has before destroying it.
727 |
728 | Type: `bool`
729 |
730 | Default: `true`
731 |
732 | ### tags
733 |
734 | Description: Key-value mapping of tags for the IAM role or user.
735 |
736 | Type: `map(any)`
737 |
738 | Default: `{}`
739 |
740 |
741 |
742 |
743 |
744 | ## Outputs
745 |
746 | | Name | Description |
747 | |------|-------------|
748 | | account\_alias | Created Account alias. |
749 | | account\_pass\_policy | Created Account password policy. |
750 | | debug\_local\_group\_inline\_policies | The transformed group inline policy map |
751 | | debug\_local\_group\_policies | The transformed group policy map |
752 | | debug\_local\_group\_policy\_arns | The transformed group policy arns map |
753 | | debug\_local\_policies | The transformed policy map |
754 | | debug\_local\_role\_inline\_policies | The transformed role inline policy map |
755 | | debug\_local\_role\_policies | The transformed role policy map |
756 | | debug\_local\_role\_policy\_arns | The transformed role policy arns map |
757 | | debug\_local\_user\_access\_keys | The transformed user access key map |
758 | | debug\_local\_user\_inline\_policies | The transformed user inline policy map |
759 | | debug\_local\_user\_policies | The transformed user policy map |
760 | | debug\_local\_user\_policy\_arns | The transformed user policy arns map |
761 | | debug\_var\_groups | The defined groups list |
762 | | debug\_var\_policies | The transformed policy map |
763 | | debug\_var\_roles | The defined roles list |
764 | | debug\_var\_users | The defined users list |
765 | | group\_inline\_policy\_attachments | Attached group inline IAM policies |
766 | | group\_policy\_arn\_attachments | Attached group IAM policy arns |
767 | | group\_policy\_attachments | Attached group customer managed IAM policies |
768 | | groups | Created IAM groups |
769 | | policies | Created customer managed IAM policies |
770 | | providers\_oidc | Created OpenID Connect providers. |
771 | | providers\_saml | Created SAML providers. |
772 | | role\_inline\_policy\_attachments | Attached role inline IAM policies |
773 | | role\_policy\_arn\_attachments | Attached role IAM policy arns |
774 | | role\_policy\_attachments | Attached role customer managed IAM policies |
775 | | roles | Created IAM roles |
776 | | user\_group\_memberships | Assigned user/group memberships |
777 | | user\_inline\_policy\_attachments | Attached user inline IAM policies |
778 | | user\_policy\_arn\_attachments | Attached user IAM policy arns |
779 | | user\_policy\_attachments | Attached user customer managed IAM policies |
780 | | users | Created IAM users |
781 |
782 |
783 |
784 |
785 | ## Related projects
786 |
787 | | GitHub | Module Registry | Description |
788 | |--------|-----------------|-------------|
789 | | [aws-iam][aws_iam_git_lnk] | [aws-iam][aws_iam_reg_lnk] | Manages AWS IAM to its full extend |
790 | | [aws-iam-roles][aws_iam_roles_git_lnk] | [aws-iam-roles][aws_iam_roles_reg_lnk] | Deprecated by [aws-iam][aws_iam_git_lnk] |
791 | | [aws-iam-cross_account][aws_iam_cross_acc_git_lnk] | [aws-iam-cross-account][aws_iam_cross_acc_reg_lnk] | Deprecated by [aws-iam][aws_iam_git_lnk] |
792 | | [aws-route53][aws_route53_git_lnk] | [aws-route53][aws_route53_reg_lnk] | Manages creation of multiple Route53 zones including attachment to new or existing delegation set |
793 | | [aws-elb][aws_elb_git_lnk] | [aws-elb][aws_elb_reg_lnk] | Manages ELB with optionally a public and/or private Route53 DNS record attached to it |
794 | | [aws-rds][aws_rds_git_lnk] | [aws-rds][aws_rds_reg_lnk] | Manages RDS resources on AWS |
795 |
796 | [aws_iam_git_lnk]: https://github.com/cytopia/terraform-aws-iam
797 | [aws_iam_reg_lnk]: https://registry.terraform.io/modules/cytopia/iam/aws
798 |
799 | [aws_iam_roles_git_lnk]: https://github.com/cytopia/terraform-aws-iam-roles
800 | [aws_iam_roles_reg_lnk]: https://registry.terraform.io/modules/cytopia/iam-roles/aws
801 |
802 | [aws_iam_cross_acc_git_lnk]: https://github.com/cytopia/terraform-aws-iam-cross-account
803 | [aws_iam_cross_acc_reg_lnk]: https://registry.terraform.io/modules/cytopia/iam-cross-account/aws
804 |
805 | [aws_route53_git_lnk]: https://github.com/cytopia/terraform-aws-route53-zone
806 | [aws_route53_reg_lnk]: https://registry.terraform.io/modules/cytopia/route53-zone/aws
807 |
808 | [aws_elb_git_lnk]: https://github.com/cytopia/terraform-aws-elb
809 | [aws_elb_reg_lnk]: https://registry.terraform.io/modules/cytopia/elb/aws
810 |
811 | [aws_rds_git_lnk]: https://github.com/cytopia/terraform-aws-rds
812 | [aws_rds_reg_lnk]: https://registry.terraform.io/modules/cytopia/rds/aws
813 |
814 |
815 | ## Authors
816 |
817 | Module managed by [cytopia](https://github.com/cytopia).
818 |
819 |
820 | ## License
821 |
822 | **[MIT License](LICENSE)**
823 |
824 | Copyright (c) 2018 **[cytopia](https://github.com/cytopia)**
825 |
--------------------------------------------------------------------------------