├── 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 | 2 | ... 3 | 4 | -------------------------------------------------------------------------------- /examples/saml-login/data/policy-sts-assume.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Action": "sts:AssumeRole", 7 | "Resource": "*" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /examples/roles/outputs.tf: -------------------------------------------------------------------------------- 1 | output "policies" { 2 | description = "Created customer managed IAM policies" 3 | value = module.aws_iam.policies 4 | } 5 | 6 | output "roles" { 7 | description = "Created roles" 8 | value = module.aws_iam.roles 9 | } 10 | -------------------------------------------------------------------------------- /examples/users/outputs.tf: -------------------------------------------------------------------------------- 1 | output "policies" { 2 | description = "Created customer managed IAM policies" 3 | value = module.aws_iam.policies 4 | } 5 | 6 | output "users" { 7 | description = "Created users" 8 | value = module.aws_iam.users 9 | } 10 | -------------------------------------------------------------------------------- /examples/groups/outputs.tf: -------------------------------------------------------------------------------- 1 | output "policies" { 2 | description = "Created customer managed IAM policies" 3 | value = module.aws_iam.policies 4 | } 5 | 6 | output "groups" { 7 | description = "Created groups" 8 | value = module.aws_iam.groups 9 | } 10 | -------------------------------------------------------------------------------- /examples/roles/data/trust-policy-file.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Action": "sts:AssumeRole", 7 | "Principal": { 8 | "AWS": [ 9 | "arn:aws:iam::1234567890:role/federation/LOGIN-ROLE" 10 | ] 11 | }, 12 | "Condition": {} 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /examples/policies-with-custom-data-sources/data/trust-policy-file.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Action": "sts:AssumeRole", 7 | "Principal": { 8 | "AWS": [ 9 | "arn:aws:iam::1234567890:role/assume/LOGIN-ROLE" 10 | ] 11 | }, 12 | "Condition": {} 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /examples/groups-users-and-policies/outputs.tf: -------------------------------------------------------------------------------- 1 | output "policies" { 2 | description = "Created customer managed IAM policies" 3 | value = module.aws_iam.policies 4 | } 5 | 6 | output "groups" { 7 | description = "Created groups" 8 | value = module.aws_iam.groups 9 | } 10 | 11 | output "users" { 12 | description = "Created users" 13 | value = module.aws_iam.users 14 | } 15 | -------------------------------------------------------------------------------- /examples/saml-login/outputs.tf: -------------------------------------------------------------------------------- 1 | output "policies" { 2 | description = "Created customer managed IAM policies" 3 | value = module.aws_iam.policies 4 | } 5 | 6 | output "roles" { 7 | description = "Created roles" 8 | value = module.aws_iam.roles 9 | } 10 | 11 | output "providers_saml" { 12 | description = "Created SAML providers." 13 | value = module.aws_iam.providers_saml 14 | } 15 | -------------------------------------------------------------------------------- /examples/saml-login/data/trust-policy-saml.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Action": "sts:AssumeRoleWithSAML", 7 | "Principal": { 8 | "Federated": "arn:aws:iam::1234567890:saml-provider/AzureAD" 9 | }, 10 | "Condition": { 11 | "StringEquals": { 12 | "SAML:aud": "https://signin.aws.amazon.com/saml" 13 | } 14 | } 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /examples/groups/data/rds-authenticate.json.tmpl: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "RDSAuthenticationAllow", 6 | "Effect": "Allow", 7 | "Action": [ 8 | "rds-db:connect" 9 | ], 10 | "Resource": [ 11 | "arn:aws:rds-db:eu-central-1:${aws_account_id}:dbuser:*/iam_user_rw", 12 | "arn:aws:rds-db:eu-central-1:${aws_account_id}:dbuser:*/iam_user_ro" 13 | ] 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /examples/policies/data/rds-authenticate.json.tmpl: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "RDSAuthenticationAllow", 6 | "Effect": "Allow", 7 | "Action": [ 8 | "rds-db:connect" 9 | ], 10 | "Resource": [ 11 | "arn:aws:rds-db:eu-central-1:${aws_account_id}:dbuser:*/iam_user_rw", 12 | "arn:aws:rds-db:eu-central-1:${aws_account_id}:dbuser:*/iam_user_ro" 13 | ] 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /examples/roles/data/rds-authenticate.json.tmpl: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "RDSAuthenticationAllow", 6 | "Effect": "Allow", 7 | "Action": [ 8 | "rds-db:connect" 9 | ], 10 | "Resource": [ 11 | "arn:aws:rds-db:eu-central-1:${aws_account_id}:dbuser:*/iam_user_rw", 12 | "arn:aws:rds-db:eu-central-1:${aws_account_id}:dbuser:*/iam_user_ro" 13 | ] 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /examples/users/data/rds-authenticate.json.tmpl: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "RDSAuthenticationAllow", 6 | "Effect": "Allow", 7 | "Action": [ 8 | "rds-db:connect" 9 | ], 10 | "Resource": [ 11 | "arn:aws:rds-db:eu-central-1:${aws_account_id}:dbuser:*/iam_user_rw", 12 | "arn:aws:rds-db:eu-central-1:${aws_account_id}:dbuser:*/iam_user_ro" 13 | ] 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /examples/groups-users-and-policies/data/rds-authenticate.json.tmpl: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "RDSAuthenticationAllow", 6 | "Effect": "Allow", 7 | "Action": [ 8 | "rds-db:connect" 9 | ], 10 | "Resource": [ 11 | "arn:aws:rds-db:eu-central-1:${aws_account_id}:dbuser:*/iam_user_rw", 12 | "arn:aws:rds-db:eu-central-1:${aws_account_id}:dbuser:*/iam_user_ro" 13 | ] 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /examples/policies/terraform.tfvars: -------------------------------------------------------------------------------- 1 | policies = [ 2 | { 3 | name = "billing-ro" 4 | path = "/assume/" 5 | desc = "Provides read-only access to billing" 6 | file = "data/billing-ro.json" 7 | vars = {} 8 | }, 9 | { 10 | name = "rds-authenticate" 11 | path = "/assume/" 12 | desc = "Allow user to authenticate to RDS via IAM" 13 | file = "data/rds-authenticate.json.tmpl" 14 | vars = { 15 | aws_account_id = "1234567890", 16 | } 17 | }, 18 | ] 19 | -------------------------------------------------------------------------------- /examples/groups/data/billing-ro.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "BillingReadOnly", 6 | "Effect": "Allow", 7 | "Action": [ 8 | "account:ListRegions", 9 | "aws-portal:View*", 10 | "awsbillingconsole:View*", 11 | "budgets:View*", 12 | "ce:Get*", 13 | "cur:Describe*", 14 | "pricing:Describe*", 15 | "pricing:Get*" 16 | ], 17 | "Resource": "*" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /examples/roles/data/billing-ro.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "BillingReadOnly", 6 | "Effect": "Allow", 7 | "Action": [ 8 | "account:ListRegions", 9 | "aws-portal:View*", 10 | "awsbillingconsole:View*", 11 | "budgets:View*", 12 | "ce:Get*", 13 | "cur:Describe*", 14 | "pricing:Describe*", 15 | "pricing:Get*" 16 | ], 17 | "Resource": "*" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /examples/users/data/billing-ro.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "BillingReadOnly", 6 | "Effect": "Allow", 7 | "Action": [ 8 | "account:ListRegions", 9 | "aws-portal:View*", 10 | "awsbillingconsole:View*", 11 | "budgets:View*", 12 | "ce:Get*", 13 | "cur:Describe*", 14 | "pricing:Describe*", 15 | "pricing:Get*" 16 | ], 17 | "Resource": "*" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /examples/policies/data/billing-ro.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "BillingReadOnly", 6 | "Effect": "Allow", 7 | "Action": [ 8 | "account:ListRegions", 9 | "aws-portal:View*", 10 | "awsbillingconsole:View*", 11 | "budgets:View*", 12 | "ce:Get*", 13 | "cur:Describe*", 14 | "pricing:Describe*", 15 | "pricing:Get*" 16 | ], 17 | "Resource": "*" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /examples/groups-users-and-policies/data/billing-ro.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "BillingReadOnly", 6 | "Effect": "Allow", 7 | "Action": [ 8 | "account:ListRegions", 9 | "aws-portal:View*", 10 | "awsbillingconsole:View*", 11 | "budgets:View*", 12 | "ce:Get*", 13 | "cur:Describe*", 14 | "pricing:Describe*", 15 | "pricing:Get*" 16 | ], 17 | "Resource": "*" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /examples/policies/variables.tf: -------------------------------------------------------------------------------- 1 | variable "policies" { 2 | description = "A list of dictionaries defining all policies." 3 | type = list(object({ 4 | name = string # Name of the policy 5 | path = string # Defaults to 'var.policy_path' if variable is set to null 6 | desc = string # Defaults to 'var.policy_desc' if variable is set to null 7 | file = string # Path to json or json.tmpl file of policy 8 | vars = map(string) # Policy template variables {key: val, ...} 9 | })) 10 | default = [] 11 | } 12 | -------------------------------------------------------------------------------- /examples/policies/main.tf: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------------------------- 2 | # AWS Settings 3 | # ------------------------------------------------------------------------------------------------- 4 | provider "aws" { 5 | region = "eu-central-1" 6 | } 7 | 8 | # ------------------------------------------------------------------------------------------------- 9 | # Modules Settings 10 | # ------------------------------------------------------------------------------------------------- 11 | module "aws_iam" { 12 | source = "../.." 13 | 14 | policies = var.policies 15 | } 16 | -------------------------------------------------------------------------------- /examples/roles/main.tf: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------------------------- 2 | # AWS Settings 3 | # ------------------------------------------------------------------------------------------------- 4 | provider "aws" { 5 | region = "eu-central-1" 6 | } 7 | 8 | # ------------------------------------------------------------------------------------------------- 9 | # Modules Settings 10 | # ------------------------------------------------------------------------------------------------- 11 | module "aws_iam" { 12 | source = "../.." 13 | 14 | policies = var.policies 15 | roles = var.roles 16 | } 17 | -------------------------------------------------------------------------------- /examples/users/main.tf: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------------------------- 2 | # AWS Settings 3 | # ------------------------------------------------------------------------------------------------- 4 | provider "aws" { 5 | region = "eu-central-1" 6 | } 7 | 8 | # ------------------------------------------------------------------------------------------------- 9 | # Modules Settings 10 | # ------------------------------------------------------------------------------------------------- 11 | module "aws_iam" { 12 | source = "../.." 13 | 14 | policies = var.policies 15 | users = var.users 16 | } 17 | -------------------------------------------------------------------------------- /examples/groups/main.tf: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------------------------- 2 | # AWS Settings 3 | # ------------------------------------------------------------------------------------------------- 4 | provider "aws" { 5 | region = "eu-central-1" 6 | } 7 | 8 | # ------------------------------------------------------------------------------------------------- 9 | # Modules Settings 10 | # ------------------------------------------------------------------------------------------------- 11 | module "aws_iam" { 12 | source = "../.." 13 | 14 | policies = var.policies 15 | groups = var.groups 16 | } 17 | -------------------------------------------------------------------------------- /examples/groups-users-and-policies/main.tf: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------------------------- 2 | # AWS Settings 3 | # ------------------------------------------------------------------------------------------------- 4 | provider "aws" { 5 | region = "eu-central-1" 6 | } 7 | 8 | # ------------------------------------------------------------------------------------------------- 9 | # Modules Settings 10 | # ------------------------------------------------------------------------------------------------- 11 | module "aws_iam" { 12 | source = "../.." 13 | 14 | policies = var.policies 15 | groups = var.groups 16 | users = var.users 17 | } 18 | -------------------------------------------------------------------------------- /examples/saml-login/main.tf: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------------------------------- 2 | # AWS Settings 3 | # ------------------------------------------------------------------------------------------------- 4 | provider "aws" { 5 | region = "eu-central-1" 6 | } 7 | 8 | # ------------------------------------------------------------------------------------------------- 9 | # Modules Settings 10 | # ------------------------------------------------------------------------------------------------- 11 | module "aws_iam" { 12 | source = "../.." 13 | 14 | providers_saml = var.providers_saml 15 | 16 | policies = var.policies 17 | roles = var.roles 18 | } 19 | -------------------------------------------------------------------------------- /examples/policies-with-custom-data-sources/terraform.tfvars: -------------------------------------------------------------------------------- 1 | roles = [ 2 | { 3 | name = "ROLE-ADMIN" 4 | path = null 5 | desc = null 6 | trust_policy_file = "data/trust-policy-file.json" 7 | permissions_boundary = null 8 | policies = [] 9 | inline_policies = [] 10 | policy_arns = [] 11 | }, 12 | { 13 | name = "ROLE-DEV" 14 | path = null 15 | desc = null 16 | trust_policy_file = "data/trust-policy-file.json" 17 | permissions_boundary = null 18 | policies = [] 19 | inline_policies = [] 20 | policy_arns = [] 21 | }, 22 | ] 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 cytopia 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/groups/variables.tf: -------------------------------------------------------------------------------- 1 | variable "policies" { 2 | description = "A list of dictionaries defining all policies." 3 | type = list(object({ 4 | name = string # Name of the policy 5 | path = string # Defaults to 'var.policy_path' if variable is set to null 6 | desc = string # Defaults to 'var.policy_desc' if variable is set to null 7 | file = string # Path to json or json.tmpl file of policy 8 | vars = map(string) # Policy template variables {key: val, ...} 9 | })) 10 | default = [] 11 | } 12 | 13 | variable "groups" { 14 | description = "A list of dictionaries defining all groups." 15 | type = list(object({ 16 | name = string # Name of the group 17 | path = string # Defaults to 'var.group_path' if variable is set to null 18 | policies = list(string) # List of names of policies (must be defined in var.policies) 19 | policy_arns = list(string) # List of existing policy ARN's 20 | inline_policies = list(object({ 21 | name = string # Name of the inline policy 22 | file = string # Path to json or json.tmpl file of policy 23 | vars = map(string) # Policy template variables {key = val, ...} 24 | })) 25 | })) 26 | default = [] 27 | } 28 | -------------------------------------------------------------------------------- /examples/saml-login/terraform.tfvars: -------------------------------------------------------------------------------- 1 | # Adding AzureAD as a login provider to AWS so 2 | # that you can login via AzureAD into the AWS account 3 | # and assume an initial role 4 | providers_saml = [ 5 | { 6 | name = "AzureAD" 7 | file = "data/provider-saml.xml" 8 | } 9 | ] 10 | 11 | # Adding a policy which allows to assume other resources 12 | policies = [ 13 | { 14 | name = "sts-assume-policy" 15 | path = "/login/assume/" 16 | desc = "Allow to assume other resources" 17 | file = "data/policy-sts-assume.json" 18 | vars = {} 19 | }, 20 | ] 21 | 22 | # Adding two roles 23 | # LOGIN-ADMIN can be assumed after AzureAD login 24 | # ASSUME-ADMIN can be assumed from LOGIN-ADMIN 25 | roles = [ 26 | { 27 | name = "LOGIN-ADMIN" 28 | path = "/login/saml/" 29 | desc = "Initial login role" 30 | trust_policy_file = "data/trust-policy-saml.json" 31 | permissions_boundary = null 32 | policies = ["sts-assume-policy"] 33 | inline_policies = [] 34 | policy_arns = [] 35 | }, 36 | { 37 | name = "ASSUME-ADMIN" 38 | path = "/assume/" 39 | desc = "Admin role" 40 | trust_policy_file = "data/trust-policy-LOGIN-ADMIN.json" 41 | permissions_boundary = null 42 | policies = [] 43 | inline_policies = [] 44 | policy_arns = ["arn:aws:iam::aws:policy/AdministratorAccess"] 45 | }, 46 | ] 47 | -------------------------------------------------------------------------------- /examples/roles/variables.tf: -------------------------------------------------------------------------------- 1 | variable "policies" { 2 | description = "A list of dictionaries defining all policies." 3 | type = list(object({ 4 | name = string # Name of the policy 5 | path = string # Defaults to 'var.policy_path' if variable is set to null 6 | desc = string # Defaults to 'var.policy_desc' if variable is set to null 7 | file = string # Path to json or json.tmpl file of policy 8 | vars = map(string) # Policy template variables {key: val, ...} 9 | })) 10 | default = [] 11 | } 12 | 13 | variable "roles" { 14 | description = "A list of dictionaries defining all roles." 15 | type = list(object({ 16 | name = string # Name of the role 17 | path = string # Defaults to 'var.role_path' if variable is set to null 18 | desc = string # Defaults to 'var.role_desc' if variable is set to null 19 | trust_policy_file = string # Path to file of trust/assume policy 20 | permissions_boundary = string # ARN to a policy used as permissions boundary (or null/empty) 21 | policies = list(string) # List of names of policies (must be defined in var.policies) 22 | policy_arns = list(string) # List of existing policy ARN's 23 | inline_policies = list(object({ 24 | name = string # Name of the inline policy 25 | file = string # Path to json or json.tmpl file of policy 26 | vars = map(string) # Policy template variables {key = val, ...} 27 | })) 28 | })) 29 | default = [] 30 | } 31 | -------------------------------------------------------------------------------- /examples/groups/terraform.tfvars: -------------------------------------------------------------------------------- 1 | policies = [ 2 | { 3 | name = "billing-ro" 4 | path = "/assume/" 5 | desc = "Provides read-only access to billing" 6 | file = "data/billing-ro.json" 7 | vars = {} 8 | }, 9 | ] 10 | 11 | groups = [ 12 | { 13 | name = "GRP-CUSTOM-POLICY" 14 | path = null 15 | policies = ["billing-ro"] 16 | policy_arns = [] 17 | inline_policies = [] 18 | }, 19 | { 20 | name = "GRP-POLICY-ARN" 21 | path = null 22 | policies = [] 23 | policy_arns = ["arn:aws:iam::aws:policy/PowerUserAccess"] 24 | inline_policies = [] 25 | }, 26 | { 27 | name = "GRP-INLINE-POLICY" 28 | path = null 29 | policies = [] 30 | policy_arns = [] 31 | inline_policies = [ 32 | { 33 | name = "rds-authenticate" 34 | file = "data/rds-authenticate.json.tmpl" 35 | vars = { 36 | aws_account_id = "1234567890" 37 | } 38 | } 39 | ] 40 | }, 41 | { 42 | name = "GRP-MULTIPLE-POLICIES" 43 | path = null 44 | policies = [] 45 | policy_arns = [ 46 | "arn:aws:iam::aws:policy/PowerUserAccess", 47 | "arn:aws:iam::aws:policy/AmazonEC2FullAccess", 48 | ] 49 | inline_policies = [ 50 | { 51 | name = "rds-authenticate" 52 | file = "data/rds-authenticate.json.tmpl" 53 | vars = { 54 | aws_account_id = "1234567890" 55 | } 56 | }, 57 | { 58 | name = "billing-ro" 59 | file = "data/billing-ro.json" 60 | vars = {} 61 | } 62 | ] 63 | }, 64 | ] 65 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # ------------------------------------------------------------------------------------------------- 4 | # Job Name 5 | # ------------------------------------------------------------------------------------------------- 6 | name: test 7 | 8 | 9 | # ------------------------------------------------------------------------------------------------- 10 | # When to run 11 | # ------------------------------------------------------------------------------------------------- 12 | on: 13 | # Runs on Pull Requests 14 | pull_request: 15 | 16 | 17 | # ------------------------------------------------------------------------------------------------- 18 | # What to run 19 | # ------------------------------------------------------------------------------------------------- 20 | jobs: 21 | test: 22 | name: Test 23 | runs-on: ubuntu-latest 24 | steps: 25 | 26 | # ------------------------------------------------------------ 27 | # Checkout repository 28 | # ------------------------------------------------------------ 29 | - name: Checkout repository 30 | uses: actions/checkout@v2 31 | with: 32 | fetch-depth: 0 33 | 34 | - name: Test 35 | run: | 36 | retry() { 37 | for n in $(seq ${RETRIES}); do 38 | echo "[${n}/${RETRIES}] ${*}"; 39 | if eval "${*}"; then 40 | echo "[SUCC] ${n}/${RETRIES}"; 41 | return 0; 42 | fi; 43 | sleep 2; 44 | echo "[FAIL] ${n}/${RETRIES}"; 45 | done; 46 | return 1; 47 | } 48 | retry make test 49 | env: 50 | RETRIES: 20 51 | -------------------------------------------------------------------------------- /examples/users/variables.tf: -------------------------------------------------------------------------------- 1 | variable "policies" { 2 | description = "A list of dictionaries defining all policies." 3 | type = list(object({ 4 | name = string # Name of the policy 5 | path = string # Defaults to 'var.policy_path' if variable is set to null 6 | desc = string # Defaults to 'var.policy_desc' if variable is set to null 7 | file = string # Path to json or json.tmpl file of policy 8 | vars = map(string) # Policy template variables {key: val, ...} 9 | })) 10 | default = [] 11 | } 12 | 13 | variable "users" { 14 | description = "A list of dictionaries defining all users." 15 | type = list(object({ 16 | name = string # Name of the user 17 | path = string # Defaults to 'var.user_path' if variable is set to null 18 | groups = list(string) # List of group names to add this user to 19 | access_keys = list(object({ 20 | name = string # IaC identifier for first or second IAM access key (not used on AWS) 21 | pgp_key = string # Leave empty for non or provide a b64-enc pubkey or keybase username 22 | status = string # 'Active' or 'Inactive' 23 | })) 24 | permissions_boundary = string # ARN to a policy used as permissions boundary (or null/empty) 25 | policies = list(string) # List of names of policies (must be defined in var.policies) 26 | policy_arns = list(string) # List of existing policy ARN's 27 | inline_policies = list(object({ 28 | name = string # Name of the inline policy 29 | file = string # Path to json or json.tmpl file of policy 30 | vars = map(string) # Policy template variables {key = val, ...} 31 | })) 32 | })) 33 | default = [] 34 | } 35 | -------------------------------------------------------------------------------- /examples/groups-users-and-policies/terraform.tfvars: -------------------------------------------------------------------------------- 1 | policies = [ 2 | { 3 | name = "billing-ro" 4 | path = "/assume/" 5 | desc = "Provides read-only access to billing" 6 | file = "data/billing-ro.json" 7 | vars = {} 8 | }, 9 | { 10 | name = "rds-authenticate" 11 | path = "/assume/" 12 | desc = "Allow user to authenticate to RDS via IAM" 13 | file = "data/rds-authenticate.json.tmpl" 14 | vars = { 15 | aws_account_id = "1234567890", 16 | } 17 | }, 18 | ] 19 | 20 | groups = [ 21 | { 22 | name = "GRP-ADMIN" 23 | path = null 24 | policies = ["rds-authenticate", "billing-ro"] 25 | policy_arns = [ 26 | "arn:aws:iam::aws:policy/AdministratorAccess", 27 | ] 28 | inline_policies = [] 29 | }, 30 | { 31 | name = "GRP-DEVELOPER" 32 | path = null 33 | policies = ["rds-authenticate"] 34 | policy_arns = [ 35 | "arn:aws:iam::aws:policy/PowerUserAccess", 36 | ] 37 | inline_policies = [] 38 | } 39 | ] 40 | 41 | users = [ 42 | { 43 | name = "john" 44 | path = "/human/" 45 | groups = ["GRP-ADMIN"] 46 | access_keys = [ 47 | { 48 | name = "key-1" 49 | pgp_key = "" 50 | status = "Active" 51 | }, 52 | ] 53 | permissions_boundary = null 54 | policies = [] 55 | inline_policies = [] 56 | policy_arns = [] 57 | }, 58 | { 59 | name = "jane" 60 | path = "/human/" 61 | groups = ["GRP-DEVELOPER"] 62 | access_keys = [ 63 | { 64 | name = "key-1" 65 | pgp_key = "" 66 | status = "Inactive" 67 | }, 68 | { 69 | name = "key-2" 70 | pgp_key = "" 71 | status = "Active" 72 | }, 73 | ] 74 | permissions_boundary = null 75 | policies = [] 76 | inline_policies = [] 77 | policy_arns = [] 78 | }, 79 | ] 80 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # ------------------------------------------------------------------------------------------------- 4 | # Job Name 5 | # ------------------------------------------------------------------------------------------------- 6 | name: lint 7 | 8 | 9 | # ------------------------------------------------------------------------------------------------- 10 | # When to run 11 | # ------------------------------------------------------------------------------------------------- 12 | on: 13 | # Runs on Pull Requests 14 | pull_request: 15 | 16 | 17 | # ------------------------------------------------------------------------------------------------- 18 | # What to run 19 | # ------------------------------------------------------------------------------------------------- 20 | jobs: 21 | lint: 22 | name: "Lint" 23 | runs-on: ubuntu-latest 24 | strategy: 25 | fail-fast: False 26 | matrix: 27 | target: 28 | - lint 29 | - gen 30 | 31 | steps: 32 | # ------------------------------------------------------------ 33 | # Setup repository 34 | # ------------------------------------------------------------ 35 | - name: Checkout repository 36 | uses: actions/checkout@v2 37 | with: 38 | fetch-depth: 0 39 | 40 | # ------------------------------------------------------------ 41 | # Lint repository 42 | # ------------------------------------------------------------ 43 | - name: "make ${{ matrix.target }}" 44 | run: | 45 | retry() { 46 | for n in $(seq ${RETRIES}); do 47 | echo "[${n}/${RETRIES}] ${*}"; 48 | if eval "${*}"; then 49 | echo "[SUCC] ${n}/${RETRIES}"; 50 | return 0; 51 | fi; 52 | sleep 2; 53 | echo "[FAIL] ${n}/${RETRIES}"; 54 | done; 55 | return 1; 56 | } 57 | 58 | retry make "${TARGET}" 59 | git diff --quiet || { echo "Build Changes"; git diff; git status; false; } 60 | env: 61 | TARGET: ${{ matrix.target }} 62 | RETRIES: 20 63 | -------------------------------------------------------------------------------- /examples/policies-with-custom-data-sources/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "us-east-1" 3 | } 4 | 5 | module "aws_iam" { 6 | source = "../../" 7 | 8 | # Note: we're using the local here as input instead 9 | roles = local.roles 10 | } 11 | 12 | locals { 13 | # Roles in this list will have the custom policy added to its policy_arns list 14 | roles_enriched = [ 15 | for role in var.roles : { 16 | name = role.name 17 | path = role.path 18 | desc = role.desc 19 | trust_policy_file = role.trust_policy_file 20 | permissions_boundary = role.permissions_boundary 21 | policies = role.policies 22 | inline_policies = role.inline_policies 23 | policy_arns = concat(role.policy_arns, [aws_iam_policy.s3.arn]) 24 | } if role["name"] == "ROLE-ADMIN" 25 | ] 26 | 27 | # Roles in this list will be left as they were (condition reversed) 28 | roles_default = [ 29 | for role in var.roles : { 30 | name = role.name 31 | path = role.path 32 | desc = role.desc 33 | trust_policy_file = role.trust_policy_file 34 | permissions_boundary = role.permissions_boundary 35 | policies = role.policies 36 | inline_policies = role.inline_policies 37 | policy_arns = role.policy_arns 38 | } if role["name"] != "ROLE-ADMIN" 39 | ] 40 | 41 | # Let's merge both created lists 42 | roles = concat(local.roles_enriched, local.roles_default) 43 | } 44 | 45 | data "aws_caller_identity" "current" {} 46 | 47 | data "aws_iam_policy_document" "s3" { 48 | statement { 49 | sid = "1" 50 | 51 | actions = [ 52 | "s3:ListAllMyBuckets", 53 | ] 54 | 55 | resources = [ 56 | "arn:aws:s3::${data.aws_caller_identity.current.account_id}:*" 57 | ] 58 | } 59 | } 60 | 61 | resource "aws_iam_policy" "s3" { 62 | name = "s3-policy" 63 | path = "/custom/" 64 | description = "Custom S3 policy" 65 | policy = data.aws_iam_policy_document.s3.json 66 | 67 | lifecycle { 68 | create_before_destroy = false 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /examples/access-key-rotation/README.md: -------------------------------------------------------------------------------- 1 | # Access key rotation 2 | 3 | This is not a terraform example, but rather a howto on how to rotate AWS access keys with this module. 4 | 5 | 6 | ## Workflow 7 | 8 | Let's assume we have the following user: 9 | 10 | `terraform.tfvars` 11 | ```hcl 12 | users = [ 13 | { 14 | name = "ci-deploy" 15 | path = null 16 | groups = [] 17 | access_keys = [ 18 | { 19 | name = "key-1" 20 | pgp_key = "" 21 | status = "Active" 22 | }, 23 | ] 24 | permissions_boundary = null 25 | policies = [] 26 | inline_policies = [] 27 | policy_arns = [ 28 | "arn:aws:iam::aws:policy/AdministratorAccess", 29 | ] 30 | } 31 | ] 32 | ``` 33 | 34 | Before rotating the access key of the `ci-deploy` user, we're going to create another access key first: 35 | 36 | `terraform.tfvars` 37 | ```hcl 38 | users = [ 39 | { 40 | name = "ci-deploy" 41 | path = null 42 | groups = [] 43 | access_keys = [ 44 | { 45 | name = "key-1" 46 | pgp_key = "" 47 | status = "Active" 48 | }, 49 | { 50 | name = "key-2" 51 | pgp_key = "" 52 | status = "Active" 53 | }, 54 | ] 55 | permissions_boundary = null 56 | policies = [] 57 | inline_policies = [] 58 | policy_arns = [ 59 | "arn:aws:iam::aws:policy/AdministratorAccess", 60 | ] 61 | } 62 | ] 63 | ``` 64 | We can then safely replace the access key and secrets at various locations. Once we have replaced them everywhere we can remove the old access key: 65 | 66 | `terraform.tfvars` 67 | ```hcl 68 | users = [ 69 | { 70 | name = "ci-deploy" 71 | path = null 72 | groups = [] 73 | access_keys = [ 74 | { 75 | name = "key-2" 76 | pgp_key = "" 77 | status = "Active" 78 | }, 79 | ] 80 | permissions_boundary = null 81 | policies = [] 82 | inline_policies = [] 83 | policy_arns = [ 84 | "arn:aws:iam::aws:policy/AdministratorAccess", 85 | ] 86 | } 87 | ] 88 | ``` 89 | 90 | ## Requirements 91 | 92 | No requirements. 93 | 94 | ## Providers 95 | 96 | No provider. 97 | 98 | ## Inputs 99 | 100 | No input. 101 | 102 | ## Outputs 103 | 104 | No output. 105 | 106 | 107 | -------------------------------------------------------------------------------- /examples/roles/terraform.tfvars: -------------------------------------------------------------------------------- 1 | policies = [ 2 | { 3 | name = "billing-ro" 4 | path = "/assume/" 5 | desc = "Provides read-only access to billing" 6 | file = "data/billing-ro.json" 7 | vars = {} 8 | }, 9 | ] 10 | 11 | roles = [ 12 | { 13 | name = "ROLE-CUSTOM-POLICY" 14 | path = null 15 | desc = null 16 | trust_policy_file = "data/trust-policy-file.json" 17 | permissions_boundary = null 18 | policies = ["billing-ro"] 19 | policy_arns = [] 20 | inline_policies = [] 21 | }, 22 | { 23 | name = "ROLE-POLICY-ARN" 24 | path = null 25 | desc = null 26 | trust_policy_file = "data/trust-policy-file.json" 27 | permissions_boundary = null 28 | policies = [] 29 | policy_arns = ["arn:aws:iam::aws:policy/PowerUserAccess"] 30 | inline_policies = [] 31 | }, 32 | { 33 | name = "ROLE-INLINE-POLICY" 34 | path = null 35 | desc = null 36 | trust_policy_file = "data/trust-policy-file.json" 37 | permissions_boundary = null 38 | policies = [] 39 | policy_arns = [] 40 | inline_policies = [ 41 | { 42 | name = "rds-authenticate" 43 | file = "data/rds-authenticate.json.tmpl" 44 | vars = { 45 | aws_account_id = "1234567890" 46 | } 47 | } 48 | ] 49 | }, 50 | { 51 | name = "ROLE-MULTIPLE-POLICIES" 52 | path = null 53 | desc = null 54 | trust_policy_file = "data/trust-policy-file.json" 55 | permissions_boundary = null 56 | policies = [] 57 | policy_arns = [ 58 | "arn:aws:iam::aws:policy/PowerUserAccess", 59 | "arn:aws:iam::aws:policy/AmazonEC2FullAccess", 60 | ] 61 | inline_policies = [ 62 | { 63 | name = "rds-authenticate" 64 | file = "data/rds-authenticate.json.tmpl" 65 | vars = { 66 | aws_account_id = "1234567890" 67 | } 68 | }, 69 | { 70 | name = "billing-ro" 71 | file = "data/billing-ro.json" 72 | vars = {} 73 | } 74 | ] 75 | }, 76 | { 77 | name = "ROLE-PERMISSION-BOUNDARY" 78 | path = null 79 | desc = null 80 | trust_policy_file = "data/trust-policy-file.json" 81 | permissions_boundary = "arn:aws:iam::aws:policy/PowerUserAccess" 82 | policies = [] 83 | policy_arns = ["arn:aws:iam::aws:policy/AdministratorAccess"] 84 | inline_policies = [] 85 | }, 86 | ] 87 | -------------------------------------------------------------------------------- /outputs-debug.tf: -------------------------------------------------------------------------------- 1 | # 2 | # This file is for development only. 3 | # Comment out the outputs to see how how the transformations work. 4 | # 5 | 6 | # ------------------------------------------------------------------------------------------------- 7 | # Input variables 8 | # ------------------------------------------------------------------------------------------------- 9 | 10 | output "debug_var_policies" { 11 | description = "The transformed policy map" 12 | value = var.policies 13 | } 14 | 15 | output "debug_var_groups" { 16 | description = "The defined groups list" 17 | value = var.groups 18 | } 19 | 20 | output "debug_var_users" { 21 | description = "The defined users list" 22 | value = var.users 23 | } 24 | 25 | output "debug_var_roles" { 26 | description = "The defined roles list" 27 | value = var.roles 28 | } 29 | 30 | 31 | # ------------------------------------------------------------------------------------------------- 32 | # Locals (policies) 33 | # ------------------------------------------------------------------------------------------------- 34 | 35 | output "debug_local_policies" { 36 | description = "The transformed policy map" 37 | value = local.policies 38 | } 39 | 40 | 41 | # ------------------------------------------------------------------------------------------------- 42 | # Locals (groups) 43 | # ------------------------------------------------------------------------------------------------- 44 | 45 | output "debug_local_group_policies" { 46 | description = "The transformed group policy map" 47 | value = local.group_policies 48 | } 49 | 50 | output "debug_local_group_inline_policies" { 51 | description = "The transformed group inline policy map" 52 | value = local.group_inline_policies 53 | } 54 | 55 | output "debug_local_group_policy_arns" { 56 | description = "The transformed group policy arns map" 57 | value = local.group_policy_arns 58 | } 59 | 60 | 61 | # ------------------------------------------------------------------------------------------------- 62 | # Locals (users) 63 | # ------------------------------------------------------------------------------------------------- 64 | 65 | output "debug_local_user_policies" { 66 | description = "The transformed user policy map" 67 | value = local.user_policies 68 | } 69 | 70 | output "debug_local_user_access_keys" { 71 | description = "The transformed user access key map" 72 | value = local.user_access_keys 73 | } 74 | 75 | output "debug_local_user_inline_policies" { 76 | description = "The transformed user inline policy map" 77 | value = local.user_inline_policies 78 | } 79 | 80 | output "debug_local_user_policy_arns" { 81 | description = "The transformed user policy arns map" 82 | value = local.user_policy_arns 83 | } 84 | 85 | 86 | # ------------------------------------------------------------------------------------------------- 87 | # Locals (roles) 88 | # ------------------------------------------------------------------------------------------------- 89 | 90 | output "debug_local_role_policies" { 91 | description = "The transformed role policy map" 92 | value = local.role_policies 93 | } 94 | 95 | output "debug_local_role_inline_policies" { 96 | description = "The transformed role inline policy map" 97 | value = local.role_inline_policies 98 | } 99 | 100 | output "debug_local_role_policy_arns" { 101 | description = "The transformed role policy arns map" 102 | value = local.role_policy_arns 103 | } 104 | -------------------------------------------------------------------------------- /examples/users/terraform.tfvars: -------------------------------------------------------------------------------- 1 | policies = [ 2 | { 3 | name = "billing-ro" 4 | path = "/assume/" 5 | desc = "Provides read-only access to billing" 6 | file = "data/billing-ro.json" 7 | vars = {} 8 | }, 9 | ] 10 | 11 | users = [ 12 | { 13 | name = "USER-CUSTOM-POLICY" 14 | path = null 15 | groups = [] 16 | access_keys = [] 17 | permissions_boundary = null 18 | policies = ["billing-ro"] 19 | policy_arns = [] 20 | inline_policies = [] 21 | }, 22 | { 23 | name = "USER-POLICY-ARN" 24 | path = null 25 | groups = [] 26 | access_keys = [] 27 | permissions_boundary = null 28 | policies = [] 29 | policy_arns = ["arn:aws:iam::aws:policy/PowerUserAccess"] 30 | inline_policies = [] 31 | }, 32 | { 33 | name = "USER-INLINE-POLICY" 34 | path = null 35 | groups = [] 36 | access_keys = [] 37 | permissions_boundary = null 38 | policies = [] 39 | policy_arns = [] 40 | inline_policies = [ 41 | { 42 | name = "rds-authenticate" 43 | file = "data/rds-authenticate.json.tmpl" 44 | vars = { 45 | aws_account_id = "1234567890" 46 | } 47 | } 48 | ] 49 | }, 50 | { 51 | name = "USER-MULTIPLE-POLICIES" 52 | path = null 53 | groups = [] 54 | access_keys = [] 55 | permissions_boundary = null 56 | policies = [] 57 | policy_arns = [ 58 | "arn:aws:iam::aws:policy/PowerUserAccess", 59 | "arn:aws:iam::aws:policy/AmazonEC2FullAccess", 60 | ] 61 | inline_policies = [ 62 | { 63 | name = "rds-authenticate" 64 | file = "data/rds-authenticate.json.tmpl" 65 | vars = { 66 | aws_account_id = "1234567890" 67 | } 68 | }, 69 | { 70 | name = "billing-ro" 71 | file = "data/billing-ro.json" 72 | vars = {} 73 | } 74 | ] 75 | }, 76 | { 77 | name = "USER-PERMISSION-BOUNDARY" 78 | path = null 79 | groups = [] 80 | access_keys = [] 81 | permissions_boundary = "arn:aws:iam::aws:policy/PowerUserAccess" 82 | policies = [] 83 | policy_arns = ["arn:aws:iam::aws:policy/AdministratorAccess"] 84 | inline_policies = [] 85 | }, 86 | { 87 | name = "USER-ACCESS-KEY-1" 88 | path = null 89 | groups = [] 90 | access_keys = [ 91 | { 92 | name = "key-1" 93 | pgp_key = "" 94 | status = "Active" 95 | }, 96 | ] 97 | permissions_boundary = null 98 | policies = [] 99 | policy_arns = ["arn:aws:iam::aws:policy/AdministratorAccess"] 100 | inline_policies = [] 101 | }, 102 | { 103 | name = "USER-ACCESS-KEY-2" 104 | path = null 105 | groups = [] 106 | access_keys = [ 107 | { 108 | name = "key-1" 109 | pgp_key = "" 110 | status = "Inactive" 111 | }, 112 | { 113 | name = "key-2" 114 | pgp_key = "" 115 | status = "Active" 116 | }, 117 | ] 118 | permissions_boundary = null 119 | policies = [] 120 | policy_arns = ["arn:aws:iam::aws:policy/AdministratorAccess"] 121 | inline_policies = [] 122 | }, 123 | ] 124 | -------------------------------------------------------------------------------- /examples/roles/README.md: -------------------------------------------------------------------------------- 1 | # Roles 2 | 3 | This example creates policies and various different roles. 4 | 5 | 6 | ## Overview 7 | 8 | Roles must be *assumed*. The AWS resource which is allowed to assume a specific role has to be defined on a per role base via its `trust_policy_file`. 9 | 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 role each. 16 | You can however create as many roles as desired. Also re-arranging them within the list will not 17 | trigger terraform to change or destroy resources as they're internally stored in a map (rather than a list) by their role names as keys (See module's `locals.tf` for transformation). 18 | 19 | ### Role assumed by another role 20 | 21 | The following defined role has administrator access on the provisioned AWS account. 22 | 23 | `terraform.tfvars` 24 | ```hcl 25 | roles = [ 26 | { 27 | name = "ROLE-ADMIN" 28 | path = null 29 | desc = null 30 | trust_policy_file = "data/trust-policies/admin.json" 31 | permissions_boundary = null 32 | policies = [] 33 | inline_policies = [] 34 | policy_arns = ["arn:aws:iam::aws:policy/AdministratorAccess"] 35 | }, 36 | ] 37 | ``` 38 | 39 | The following trust policy allows to assume the above defined role, from a role named `LOGIN-ADMIN` in the AWS account `1234567890`. 40 | 41 | `data/trust-policies/admin.json` 42 | ```json 43 | { 44 | "Version": "2012-10-17", 45 | "Statement": [ 46 | { 47 | "Effect": "Allow", 48 | "Action": "sts:AssumeRole", 49 | "Principal": { 50 | "AWS": [ 51 | "arn:aws:iam::1234567890:role/federation/LOGIN-ADMIN" 52 | ] 53 | }, 54 | "Condition": {} 55 | } 56 | ] 57 | } 58 | ``` 59 | 60 | 61 | ## Usage 62 | 63 | To run this example you need to execute: 64 | 65 | ```bash 66 | $ terraform init 67 | $ terraform plan 68 | $ terraform apply 69 | ``` 70 | 71 | Note that this example may create resources which cost money. Run terraform destroy when you don't need these resources. 72 | 73 | 74 | 75 | ## Requirements 76 | 77 | No requirements. 78 | 79 | ## Providers 80 | 81 | No provider. 82 | 83 | ## Inputs 84 | 85 | | Name | Description | Type | Default | Required | 86 | |------|-------------|------|---------|:--------:| 87 | | 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 | 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 = "" 11 | # path = "" 12 | # desc = "" 13 | # file = "" 14 | # vars = { 15 | # key = "val", 16 | # } 17 | # }, 18 | # { 19 | # name = "" 20 | # path = "" 21 | # desc = "" 22 | # file = "" 23 | # vars = { 24 | # key = "val", 25 | # } 26 | # }, 27 | # } 28 | # 29 | # Into the following format: 30 | # 31 | # policies = { 32 | # "" = { 33 | # name = "" 34 | # path = "" 35 | # desc = "" 36 | # file = "" 37 | # vars = { 38 | # key = "val", 39 | # } 40 | # } 41 | # "" = { 42 | # name = "" 43 | # path = "" 44 | # desc = "" 45 | # file = "" 46 | # vars = { 47 | # key = "val", 48 | # } 49 | # } 50 | # } 51 | policies = { for i, v in var.policies : var.policies[i]["name"] => v } 52 | } 53 | 54 | 55 | # ------------------------------------------------------------------------------------------------- 56 | # Group/User/Role Policy transformations 57 | # ------------------------------------------------------------------------------------------------- 58 | 59 | locals { 60 | # [group_policies] 61 | # This local combines var.groups and var.policies and creates its own list as shown below: 62 | # 63 | # group_policies = [ 64 | # { 65 | # ":" = { 66 | # name = "" 67 | # path = "" 68 | # desc = "" 69 | # file = "" 70 | # vars = { 71 | # key = "val", 72 | # } 73 | # } 74 | # }, 75 | # ] 76 | gp = flatten([ 77 | for group in var.groups : [ 78 | for policy in group["policies"] : { 79 | group_name = group.name 80 | policy_name = policy 81 | policy = local.policies[policy] 82 | } 83 | ] 84 | ]) 85 | group_policies = { for obj in local.gp : "${obj.group_name}:${obj.policy_name}" => obj.policy } 86 | 87 | # [user_policies] 88 | # This local combines var.users and var.policies and creates its own list as shown below: 89 | # 90 | # user_policies = [ 91 | # { 92 | # ":" = { 93 | # name = "" 94 | # path = "" 95 | # desc = "" 96 | # file = "" 97 | # vars = { 98 | # key = "val", 99 | # } 100 | # } 101 | # }, 102 | # ] 103 | up = flatten([ 104 | for user in var.users : [ 105 | for policy in user["policies"] : { 106 | user_name = user.name 107 | policy_name = policy 108 | policy = local.policies[policy] 109 | } 110 | ] 111 | ]) 112 | user_policies = { for obj in local.up : "${obj.user_name}:${obj.policy_name}" => obj.policy } 113 | 114 | # [role_policies] 115 | # This local combines var.roles and var.policies and creates its own list as shown below: 116 | # 117 | # role_policies = [ 118 | # { 119 | # ":" = { 120 | # name = "" 121 | # path = "" 122 | # desc = "" 123 | # file = "" 124 | # vars = { 125 | # key = "val", 126 | # } 127 | # } 128 | # }, 129 | # ] 130 | rp = flatten([ 131 | for role in var.roles : [ 132 | for policy in role["policies"] : { 133 | role_name = role.name 134 | policy_name = policy 135 | policy = local.policies[policy] 136 | } 137 | ] 138 | ]) 139 | role_policies = { for obj in local.rp : "${obj.role_name}:${obj.policy_name}" => obj.policy } 140 | } 141 | 142 | 143 | # ------------------------------------------------------------------------------------------------- 144 | # Role/User Inline Policy transformations 145 | # ------------------------------------------------------------------------------------------------- 146 | 147 | locals { 148 | # [group_inline_policies] 149 | # This local extracts inline_group_policies from var.groups and combines the found policies 150 | # with the group names as shown below: 151 | # 152 | # group_inline_policies = [ 153 | # { 154 | # ":" = { 155 | # name = "" 156 | # file = "" 157 | # vars = { 158 | # key = "val", 159 | # } 160 | # } 161 | # }, 162 | # { 163 | # ":" = { 164 | # name = "" 165 | # file = "" 166 | # vars = { 167 | # key = "val", 168 | # } 169 | # } 170 | # }, 171 | # ] 172 | gip = flatten([ 173 | for group in var.groups : [ 174 | for inline_policy in group["inline_policies"] : { 175 | group_name = group.name 176 | policy_name = inline_policy["name"] 177 | policy = inline_policy 178 | } 179 | ] 180 | ]) 181 | group_inline_policies = { for obj in local.gip : "${obj.group_name}:${obj.policy_name}" => obj.policy } 182 | 183 | # [user_inline_policies] 184 | # This local extracts inline_user_policies from var.users and combines the found policies 185 | # with the user names as shown below: 186 | # 187 | # user_inline_policies = [ 188 | # { 189 | # ":" = { 190 | # name = "" 191 | # file = "" 192 | # vars = { 193 | # key = "val", 194 | # } 195 | # } 196 | # }, 197 | # { 198 | # ":" = { 199 | # name = "" 200 | # file = "" 201 | # vars = { 202 | # key = "val", 203 | # } 204 | # } 205 | # }, 206 | # ] 207 | uip = flatten([ 208 | for user in var.users : [ 209 | for inline_policy in user["inline_policies"] : { 210 | user_name = user.name 211 | policy_name = inline_policy["name"] 212 | policy = inline_policy 213 | } 214 | ] 215 | ]) 216 | user_inline_policies = { for obj in local.uip : "${obj.user_name}:${obj.policy_name}" => obj.policy } 217 | 218 | # [role_inline_policies] 219 | # This local extracts inline_role_policies from var.roles and combines the found policies 220 | # with the role names as shown below: 221 | # 222 | # role_inline_policies = [ 223 | # { 224 | # ":" = { 225 | # name = "" 226 | # file = "" 227 | # vars = { 228 | # key = "val", 229 | # } 230 | # } 231 | # }, 232 | # { 233 | # ":" = { 234 | # name = "" 235 | # file = "" 236 | # vars = { 237 | # key = "val", 238 | # } 239 | # } 240 | # }, 241 | # ] 242 | rip = flatten([ 243 | for role in var.roles : [ 244 | for inline_policy in role["inline_policies"] : { 245 | role_name = role.name 246 | policy_name = inline_policy["name"] 247 | policy = inline_policy 248 | } 249 | ] 250 | ]) 251 | role_inline_policies = { for obj in local.rip : "${obj.role_name}:${obj.policy_name}" => obj.policy } 252 | } 253 | 254 | 255 | # ------------------------------------------------------------------------------------------------- 256 | # Group/User/Role Policy Arn transformations 257 | # ------------------------------------------------------------------------------------------------- 258 | 259 | locals { 260 | # [group_policy_arns] 261 | # This local extracts policy_arns from var.groups and combines the found policies 262 | # with the group names as shown below: 263 | # 264 | # group_policy_arns = [ 265 | # { 266 | # ":" = "" 267 | # }, 268 | # { 269 | # ":" = "" 270 | # }, 271 | # ] 272 | gpa = flatten([ 273 | for group in var.groups : [ 274 | for policy_arn in group["policy_arns"] : { 275 | group_name = group.name 276 | policy_arn = policy_arn 277 | policy = policy_arn 278 | } 279 | ] 280 | ]) 281 | group_policy_arns = { for obj in local.gpa : "${obj.group_name}:${obj.policy_arn}" => obj.policy } 282 | 283 | # [user_policy_arns] 284 | # This local extracts policy_arns from var.users and combines the found policies 285 | # with the user names as shown below: 286 | # 287 | # user_policy_arns = [ 288 | # { 289 | # ":" = "" 290 | # }, 291 | # { 292 | # ":" = "" 293 | # }, 294 | # ] 295 | upa = flatten([ 296 | for user in var.users : [ 297 | for policy_arn in user["policy_arns"] : { 298 | user_name = user.name 299 | policy_arn = policy_arn 300 | policy = policy_arn 301 | } 302 | ] 303 | ]) 304 | user_policy_arns = { for obj in local.upa : "${obj.user_name}:${obj.policy_arn}" => obj.policy } 305 | 306 | # [role_policy_arns] 307 | # This local extracts policy_arns from var.roles and combines the found policies 308 | # with the role names as shown below: 309 | # 310 | # role_policy_arns = [ 311 | # { 312 | # ":" = "" 313 | # }, 314 | # { 315 | # ":" = "" 316 | # }, 317 | # ] 318 | rpa = flatten([ 319 | for role in var.roles : [ 320 | for policy_arn in role["policy_arns"] : { 321 | role_name = role.name 322 | policy_arn = policy_arn 323 | policy = policy_arn 324 | } 325 | ] 326 | ]) 327 | role_policy_arns = { for obj in local.rpa : "${obj.role_name}:${obj.policy_arn}" => obj.policy } 328 | } 329 | 330 | 331 | # ------------------------------------------------------------------------------------------------- 332 | # User Access key transformations 333 | # ------------------------------------------------------------------------------------------------- 334 | 335 | locals { 336 | # [user_access_keys] 337 | # This local transforms users into a useable user_access_keys list 338 | # 339 | # user_access_keys = [ 340 | # { 341 | # ":" = { 342 | # user_name = "" # required to distinguish between one of the two keys 343 | # key_name = "" # required to distinguish between one of the two keys 344 | # pgp_key = "" # or empty 345 | # status = "Active|Inactive" # or empty 346 | # }, 347 | # }, 348 | # { 349 | # ":" = { 350 | # user_name = "" # required to distinguish between one of the two keys 351 | # key_name = "" # required to distinguish between one of the two keys 352 | # pgp_key = "" # or empty 353 | # status = "Active|Inactive" # or empty 354 | # }, 355 | # }, 356 | # ] 357 | uak = flatten([ 358 | for user in var.users : [ 359 | for user_access_key in user["access_keys"] : { 360 | user_name = user.name 361 | key_name = user_access_key.name 362 | pgp_key = user_access_key.pgp_key 363 | status = user_access_key.status 364 | } 365 | ] 366 | ]) 367 | user_access_keys = { for obj in local.uak : "${obj.user_name}:${obj.key_name}" => obj } 368 | } 369 | -------------------------------------------------------------------------------- /examples/saml-login/README.md: -------------------------------------------------------------------------------- 1 | # SAML Login 2 | 3 | Use Microsoft Azure, ADFS or any other compatible SAML provider to log in into AWS. 4 | 5 | ## Overview 6 | 7 | This example adds SAML as an identity provider to log into AWS into an initially assumed *login role* and be able to assume roles in various other AWS accounts from there. 8 | 9 | The initial assumed *login role* (after login) does not have any permissions assigned except one to assume further into other roles. Then the other assumed roles explicitly state that they can only be assumed by one of the initially assumed *login roles*. 10 | 11 | 12 | ## Best-practice 13 | 14 | It is a good idea to separate *logins* and *permissions* by using different AWS accounts. 15 | 16 | 1. Login Account: Users (from an identity provider) can login and assume a *login role* with no permissions (except assume). Both roles and identity provider are defined in this account. 17 | 2. Other accounts: These accounts define roles that can be assumed from the *login role* and will have actual permissions. 18 | 19 | Keep in mind that you might still want to have access to the *login account* and should therefore also create *assume roles* to ensure *login* and *assume* roles are completely separated (even in the same account). 20 | 21 | ## Example 22 | 23 | ### `terraform.tfvars` 24 | ```hcl 25 | # Adding AzureAD as a login provider to AWS so 26 | # that you can login via AzureAD into the AWS account 27 | # and assume an initial role 28 | providers_saml = [ 29 | { 30 | name = "AzureAD" 31 | file = "data/provider-saml.xml" 32 | } 33 | ] 34 | 35 | # Adding a policy which allows to assume other resources 36 | policies = [ 37 | { 38 | name = "sts-assume-policy" 39 | path = "/login/assume/" 40 | desc = "Allow to assume other resources" 41 | file = "data/policy-sts-assume.json" 42 | vars = {} 43 | }, 44 | ] 45 | 46 | # Adding two roles 47 | # LOGIN-ADMIN can be assumed after AzureAD login 48 | # ASSUME-ADMIN can be assumed from LOGIN-ADMIN 49 | roles = [ 50 | { 51 | name = "LOGIN-ADMIN" 52 | path = "/login/saml/" 53 | desc = "Initial login role" 54 | trust_policy_file = "data/trust-policy-saml.json" 55 | permissions_boundary = null 56 | policies = ["sts-assume-policy"] 57 | inline_policies = [] 58 | policy_arns = [] 59 | }, 60 | { 61 | name = "ASSUME-ADMIN" 62 | path = "/assume/" 63 | desc = "Admin role" 64 | trust_policy_file = "data/trust-policy-LOGIN-ADMIN.json" 65 | permissions_boundary = null 66 | policies = [] 67 | inline_policies = [] 68 | policy_arns = ["arn:aws:iam::aws:policy/AdministratorAccess"] 69 | }, 70 | ] 71 | ``` 72 | 73 | 74 | ### `data/provider-saml.xml` 75 | 76 | The SAML provider file will be provided by the SAML service. The following is just a simple extract: 77 | ```xml 78 | 79 | ... 80 | 81 | ``` 82 | 83 | ### `data/trust-policy-saml.json` 84 | 85 | The trust policy file allows the `LOGIN-ADMIN` role to be assumed by the login provider `AzureAD`. 86 | 87 | > Roles must be *assumed*. The AWS resource which is allowed to assume a specific role has to be defined on a per role base via its `trust_policy_file`. 88 | 89 | ```json 90 | { 91 | "Version": "2012-10-17", 92 | "Statement": [ 93 | { 94 | "Effect": "Allow", 95 | "Action": "sts:AssumeRoleWithSAML", 96 | "Principal": { 97 | "Federated": "arn:aws:iam::1234567890:saml-provider/AzureAD" 98 | }, 99 | "Condition": { 100 | "StringEquals": { 101 | "SAML:aud": "https://signin.aws.amazon.com/saml" 102 | } 103 | } 104 | } 105 | ] 106 | } 107 | ``` 108 | 109 | ### `data/policy-sts-assume.json` 110 | 111 | The `sts-assume-policy` attached to the `LOGIN-ADMIN` role allows it to further assume into other resources. 112 | 113 | > Policies define certain permissions and can be attached to an AWS resource, which will then be allowed those permissions. 114 | 115 | ```json 116 | { 117 | "Version": "2012-10-17", 118 | "Statement": [ 119 | { 120 | "Effect": "Allow", 121 | "Action": "sts:AssumeRole", 122 | "Resource": "*" 123 | } 124 | ] 125 | } 126 | ``` 127 | 128 | ### `data/trust-policy-LOGIN-ADMIN.json` 129 | 130 | The trust policy file allows the `ASSUME-ADMIN` role to be assumed by the `LOGIN-ADMIN` role: 131 | 132 | > Roles must be *assumed*. The AWS resource which is allowed to assume a specific role has to be defined on a per role base via its `trust_policy_file`. 133 | 134 | ```json 135 | { 136 | "Version": "2012-10-17", 137 | "Statement": [ 138 | { 139 | "Effect": "Allow", 140 | "Action": "sts:AssumeRole", 141 | "Principal": { 142 | "AWS": [ 143 | "arn:aws:iam::1234567890:role/login/saml/LOGIN-ADMIN" 144 | ] 145 | }, 146 | "Condition": {} 147 | } 148 | ] 149 | } 150 | ``` 151 | 152 | 153 | ## Usage 154 | 155 | To run this example you need to execute: 156 | 157 | ```bash 158 | $ terraform init 159 | $ terraform plan 160 | $ terraform apply 161 | ``` 162 | 163 | Note that this example may create resources which cost money. Run terraform destroy when you don't need these resources. 164 | 165 | 166 | 167 | ## Requirements 168 | 169 | No requirements. 170 | 171 | ## Providers 172 | 173 | No provider. 174 | 175 | ## Inputs 176 | 177 | | Name | Description | Type | Default | Required | 178 | |------|-------------|------|---------|:--------:| 179 | | 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 | 180 | | 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 | 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 | [![lint](https://github.com/cytopia/terraform-aws-iam/workflows/lint/badge.svg)](https://github.com/cytopia/terraform-aws-iam/actions?query=workflow%3Alint) 14 | [![test](https://github.com/cytopia/terraform-aws-iam/workflows/test/badge.svg)](https://github.com/cytopia/terraform-aws-iam/actions?query=workflow%3Atest) 15 | [![Tag](https://img.shields.io/github/tag/cytopia/terraform-aws-iam.svg)](https://github.com/cytopia/terraform-aws-iam/releases) 16 | [![Terraform](https://img.shields.io/badge/Terraform--registry-aws--iam-brightgreen.svg)](https://registry.terraform.io/modules/cytopia/iam/aws/) 17 | [![License](https://img.shields.io/badge/license-MIT-blue.svg)](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 | --------------------------------------------------------------------------------