Skip to content

cytopia/terraform-aws-iam

Repository files navigation

Terraform module: AWS IAM

Features | Important | Examples | Usage | Inputs | Outputs | Related projects | Authors | License

lint test Tag Terraform License

This Terraform module manages AWS IAM to its full extend.

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.

⭐ Features

  • Completely configurable via terraform.tfvars only
  • Arbitrary number of IAM policies, groups, users and roles
  • Policies can be defined via JSON or templatable JSON files
  • Policies can be defined via aws_iam_policy_document (Example here)
  • Groups, users and roles can be attached to an arbitrary number of custom policies, inline policies and existing policy ARN's
  • Users can be added to an arbitrary number of groups
  • Users support AWS access/secret key rotation
  • Roles support trusted entities
  • Arbitrary number of identity providers (SAML and OIDC)
  • Account settings: account alias and password policy

❗ Important

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. This is either an issue with the terraform resource aws_iam_access_key or with the AWS api itself.

💡 Examples

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.

📄 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.

Example Description
POLICIES
JSON policies Define JSON policies with variable templating
Policies with custom data sources Use terraform's aws_iam_policy_document data source to create policies and attach them to defined roles.
GROUPS / USERS
Groups Defines groups
Users Defines users
Groups, users and policies Defines groups, users and policies
Access key rotation Shows how to safely rotate AWS access keys for IAM users
ROLES
Roles Define roles (cross-account assumable)
ADVANCED
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.

💻 Usage

  1. Use terraform.tfvars only
  2. Use Module
  3. Use Terragrunt

Use terraform.tfvars only

You can simply clone this repository and add your terraform.tfvars file into the root of this directory.

terraform.tfvars

# --------------------------------------------------------------------------------
# Account Management
# --------------------------------------------------------------------------------

account_alias = "prod-account"

account_pass_policy = {
  manage                         = true
  allow_users_to_change_password = true
  hard_expiry                    = false
  max_password_age               = 365
  minimum_password_length        = 8
  password_reuse_prevention      = 5
  require_lowercase_characters   = true
  require_numbers                = true
  require_symbols                = true
  require_uppercase_characters   = true
}

# --------------------------------------------------------------------------------
# Account Identity provider
# --------------------------------------------------------------------------------

# Add a SAML provider for login
providers_saml = [
  {
    name = "AzureAD"
    file = "path/to/azure/meta.xml"
  },
  {
    name = "ADFS"
    file = "path/to/adfs/meta.xml"
  }
]

# --------------------------------------------------------------------------------
# Policies, Groups, Users and Roles
# --------------------------------------------------------------------------------

# List of policies to create
# Policies defined here can be used by name in groups, users and roles list
policies = [
  {
    name = "ro-billing"
    path = "/assume/human/"
    desc = "Provides read-only access to billing"
    file = "policies/ro-billing.json"
    vars = {}
  },
]

# List of groups to manage
# Groups defined here can be used in users list
groups = [
  {
    name                 = "admin-group"
    path                 = null
    policies             = []
    policy_arns = [
      "arn:aws:iam::aws:policy/AdministratorAccess",
    ]
    inline_policies      = []
  },
]

# List of users to manage
users = [
  {
    name                 = "admin"
    path                 = null
    groups               = ["admin-group"]
    access_keys          = []
    permissions_boundary = null
    policies             = []
    policy_arns          = []
    inline_policies      = []
  },
]

# List of roles to manage
roles = [
  {
    name                 = "ROLE-ADMIN"
    path                 = ""
    desc                 = ""
    trust_policy_file    = "trust-policies/admin.json"
    permissions_boundary = null
    policies             = []
    policy_arns = [
      "arn:aws:iam::aws:policy/AdministratorAccess",
    ]
    inline_policies      = []
  },
  {
    name                 = "ROLE-DEV"
    path                 = ""
    desc                 = ""
    trust_policy_file    = "trust-policies/dev.json"
    permissions_boundary = "arn:aws:iam::aws:policy/PowerUserAccess"
    policies = [
      "ro-billing",
    ]
    policy_arns = [
      "arn:aws:iam::aws:policy/PowerUserAccess",
    ]
    inline_policies      = []
  },
]

# --------------------------------------------------------------------------------
# Defaults
# --------------------------------------------------------------------------------

policy_path = "/"
policy_desc = "Managed by Terraform"
group_path  = "/"
user_path   = "/"
role_path   = "/"
role_desc   = "Managed by Terraform"

role_max_session_duration  = 3600
role_force_detach_policies = true

tags = {
  env   = "prod"
  owner = "terraform"
}

Use Module

Create your own module by sourcing this module.

module "iam_roles" {
  source = "github.com/cytopia/terraform-aws-iam?ref=v5.0.5"

  # --------------------------------------------------------------------------------
  # Account Management
  # --------------------------------------------------------------------------------

  account_alias = "prod-account"

  account_pass_policy = {
    manage                         = true
    allow_users_to_change_password = true
    hard_expiry                    = false
    max_password_age               = 365
    minimum_password_length        = 8
    password_reuse_prevention      = 5
    require_lowercase_characters   = true
    require_numbers                = true
    require_symbols                = true
    require_uppercase_characters   = true
  }

  # --------------------------------------------------------------------------------
  # Account Identity provider
  # --------------------------------------------------------------------------------

  # Add a SAML provider for login
  providers_saml = [
    {
      name = "AzureAD"
      file = "path/to/azure/meta.xml"
    },
    {
      name = "ADFS"
      file = "path/to/adfs/meta.xml"
    }
  ]

  # --------------------------------------------------------------------------------
  # Policies, Groups, Users and Roles
  # --------------------------------------------------------------------------------

  # List of policies to create
  # Policies defined here can be used by name in groups, users and roles list
  policies = [
    {
      name = "ro-billing"
      path = "/assume/human/"
      desc = "Provides read-only access to billing"
      file = "policies/ro-billing.json"
      vars = {}
    },
  ]

  # List of groups to manage
  # Groups defined here can be used in users list
  groups = [
    {
      name                 = "admin-group"
      path                 = null
      policies             = []
      policy_arns = [
        "arn:aws:iam::aws:policy/AdministratorAccess",
      ]
      inline_policies      = []
    },
  ]

  # List of users to manage
  users = [
    {
      name                 = "admin"
      path                 = null
      groups               = ["admin-group"]
      access_keys          = []
      permissions_boundary = null
      policies             = []
      policy_arns          = []
      inline_policies      = []
    },
  ]

  # List of roles to manage
  roles = [
    {
      name                 = "ROLE-ADMIN"
      path                 = ""
      desc                 = ""
      trust_policy_file    = "trust-policies/admin.json"
      permissions_boundary = null
      policies             = []
      policy_arns = [
        "arn:aws:iam::aws:policy/AdministratorAccess",
      ]
      inline_policies      = []
    },
    {
      name                 = "ROLE-DEV"
      path                 = ""
      desc                 = ""
      trust_policy_file    = "trust-policies/dev.json"
      permissions_boundary = "arn:aws:iam::aws:policy/PowerUserAccess"
      policies = [
        "ro-billing",
      ]
      policy_arns = [
        "arn:aws:iam::aws:policy/PowerUserAccess",
      ]
      inline_policies      = []
    },
  ]

  # --------------------------------------------------------------------------------
  # Defaults
  # --------------------------------------------------------------------------------

  policy_path = "/"
  policy_desc = "Managed by Terraform"
  group_path  = "/"
  user_path   = "/"
  role_path   = "/"
  role_desc   = "Managed by Terraform"

  role_max_session_duration  = 3600
  role_force_detach_policies = true

  tags = {
    env   = "prod"
    owner = "terraform"
  }
}

Use Terragrunt

Wrap this module into Terragrunt

terraform {
  source = "github.com/cytopia/terraform-aws-iam?ref=v5.0.5"
}

inputs = {
  # --------------------------------------------------------------------------------
  # Account Management
  # --------------------------------------------------------------------------------

  account_alias = "prod-account"

  account_pass_policy = {
    manage                         = true
    allow_users_to_change_password = true
    hard_expiry                    = false
    max_password_age               = 365
    minimum_password_length        = 8
    password_reuse_prevention      = 5
    require_lowercase_characters   = true
    require_numbers                = true
    require_symbols                = true
    require_uppercase_characters   = true
  }

  # --------------------------------------------------------------------------------
  # Account Identity provider
  # --------------------------------------------------------------------------------

  # Add a SAML providers for login
  providers_saml = [
    {
      name = "AzureAD"
      file = "path/to/azure/meta.xml"
    },
    {
      name = "ADFS"
      file = "path/to/adfs/meta.xml"
    }
  ]

  # --------------------------------------------------------------------------------
  # Policies, Groups, Users and Roles
  # --------------------------------------------------------------------------------

  # List of policies to create
  # Policies defined here can be used by name in groups, users and roles list
  policies = [
    {
      name = "ro-billing"
      path = "/assume/human/"
      desc = "Provides read-only access to billing"
      file = "policies/ro-billing.json"
      vars = {}
    },
  ]

  # List of groups to manage
  # Groups defined here can be used in users list
  groups = [
    {
      name                 = "admin-group"
      path                 = null
      policies             = []
      policy_arns = [
        "arn:aws:iam::aws:policy/AdministratorAccess",
      ]
      inline_policies      = []
    },
  ]

  # List of users to manage
  users = [
    {
      name                 = "admin"
      path                 = null
      groups               = ["admin-group"]
      access_keys          = []
      permissions_boundary = null
      policies             = []
      policy_arns          = []
      inline_policies      = []
    },
  ]

  # List of roles to manage
  roles = [
    {
      name                 = "ROLE-ADMIN"
      path                 = ""
      desc                 = ""
      trust_policy_file    = "trust-policies/admin.json"
      permissions_boundary = null
      policies             = []
      policy_arns = [
        "arn:aws:iam::aws:policy/AdministratorAccess",
      ]
      inline_policies      = []
    },
    {
      name                 = "ROLE-DEV"
      path                 = ""
      desc                 = ""
      trust_policy_file    = "trust-policies/dev.json"
      permissions_boundary = "arn:aws:iam::aws:policy/PowerUserAccess"
      policies = [
        "ro-billing",
      ]
      policy_arns = [
        "arn:aws:iam::aws:policy/PowerUserAccess",
      ]
      inline_policies      = []
    },
  ]

  # --------------------------------------------------------------------------------
  # Defaults
  # --------------------------------------------------------------------------------

  policy_path = "/"
  policy_desc = "Managed by Terraform"
  group_path  = "/"
  user_path   = "/"
  role_path   = "/"
  role_desc   = "Managed by Terraform"

  role_max_session_duration  = 3600
  role_force_detach_policies = true

  tags = {
    env   = "prod"
    owner = "terraform"
  }
}

Required Inputs

No required input.

Optional Inputs

The following input variables are optional (have default values):

account_alias

Description: Assign the account alias for the AWS Account. Unmanaged by default. Resource will be created if the string is non-empty.

Type: string

Default: ""

account_pass_policy

Description: Manages Password Policy for the AWS Account. Unmanaged by default. Resource will be created if 'manage' is set to true.

Type:

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?
  })

Default:

{
  "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
}

providers_saml

Description: A list of dictionaries defining saml providers.

Type:

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
  }))

Default: []

providers_oidc

Description: A list of dictionaries defining openid connect providers.

Type:

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.
  }))

Default: []

policies

Description: A list of dictionaries defining all policies.

Type:

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, ...}
  }))

Default: []

groups

Description: A list of dictionaries defining all groups.

Type:

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, ...}
    }))
  }))

Default: []

users

Description: A list of dictionaries defining all users.

Type:

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, ...}
    }))
  }))

Default: []

roles

Description: A list of dictionaries defining all roles.

Type:

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, ...}
    }))
  }))

Default: []

policy_path

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.

Type: string

Default: "/"

policy_desc

Description: The default description of the policy.

Type: string

Default: "Managed by Terraform"

group_path

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.

Type: string

Default: "/"

user_path

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.

Type: string

Default: "/"

role_path

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.

Type: string

Default: "/"

role_desc

Description: The description of the role.

Type: string

Default: "Managed by Terraform"

role_max_session_duration

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.

Type: string

Default: "3600"

role_force_detach_policies

Description: Specifies to force detaching any policies the role has before destroying it.

Type: bool

Default: true

tags

Description: Key-value mapping of tags for the IAM role or user.

Type: map(any)

Default: {}

Outputs

Name Description
account_alias Created Account alias.
account_pass_policy Created Account password policy.
debug_local_group_inline_policies The transformed group inline policy map
debug_local_group_policies The transformed group policy map
debug_local_group_policy_arns The transformed group policy arns map
debug_local_policies The transformed policy map
debug_local_role_inline_policies The transformed role inline policy map
debug_local_role_policies The transformed role policy map
debug_local_role_policy_arns The transformed role policy arns map
debug_local_user_access_keys The transformed user access key map
debug_local_user_inline_policies The transformed user inline policy map
debug_local_user_policies The transformed user policy map
debug_local_user_policy_arns The transformed user policy arns map
debug_var_groups The defined groups list
debug_var_policies The transformed policy map
debug_var_roles The defined roles list
debug_var_users The defined users list
group_inline_policy_attachments Attached group inline IAM policies
group_policy_arn_attachments Attached group IAM policy arns
group_policy_attachments Attached group customer managed IAM policies
groups Created IAM groups
policies Created customer managed IAM policies
providers_oidc Created OpenID Connect providers.
providers_saml Created SAML providers.
role_inline_policy_attachments Attached role inline IAM policies
role_policy_arn_attachments Attached role IAM policy arns
role_policy_attachments Attached role customer managed IAM policies
roles Created IAM roles
user_group_memberships Assigned user/group memberships
user_inline_policy_attachments Attached user inline IAM policies
user_policy_arn_attachments Attached user IAM policy arns
user_policy_attachments Attached user customer managed IAM policies
users Created IAM users

Related projects

GitHub Module Registry Description
aws-iam aws-iam Manages AWS IAM to its full extend
aws-iam-roles aws-iam-roles Deprecated by aws-iam
aws-iam-cross_account aws-iam-cross-account Deprecated by aws-iam
aws-route53 aws-route53 Manages creation of multiple Route53 zones including attachment to new or existing delegation set
aws-elb aws-elb Manages ELB with optionally a public and/or private Route53 DNS record attached to it
aws-rds aws-rds Manages RDS resources on AWS

Authors

Module managed by cytopia.

License

MIT License

Copyright (c) 2018 cytopia