diff --git a/README.md b/README.md index b86cf5bb..f7e0dc71 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,103 @@ -# terraform-aws-iam -Terraform module which creates IAM resources on AWS - -- [ ] IAM account -- [ ] IAM groups -- [ ] IAM users -- [ ] IAM assumable roles -- [ ] IAM group with assumable roles policy +# AWS Identity and Access Management (IAM) Terraform module + +These types of resources are supported: + +* [IAM account alias](https://www.terraform.io/docs/providers/aws/r/iam_account_alias.html) +* [IAM password policy](https://www.terraform.io/docs/providers/aws/r/iam_account_password_policy.html) +* [IAM user](https://www.terraform.io/docs/providers/aws/r/iam_user.html) +* [IAM user login profile](https://www.terraform.io/docs/providers/aws/r/iam_user_login_profile.html) +* [IAM group](https://www.terraform.io/docs/providers/aws/r/iam_group.html) +* [IAM role](https://www.terraform.io/docs/providers/aws/r/iam_role.html) + +## Usage + +`iam-account`: + +```hcl +module "iam_account" { + source = "terraform-aws-modules/iam/aws//modules/iam-account" + + account_alias = "awesome-company" + + minimum_password_length = 37 + require_numbers = false +} +``` + +`iam-assumable-roles`: +```hcl +module "iam_assumable_roles" { + source = "terraform-aws-modules/iam/aws//modules/iam-assumable-roles" + + trusted_role_arns = [ + "arn:aws:iam::307990089504:root", + "arn:aws:iam::835367859851:user/anton", + ] + + create_admin_role = true + + create_poweruser_role = true + poweruser_role_name = "developer" + + create_readonly_role = true + readonly_role_requires_mfa = false +} +``` + +`iam-user`: +```hcl +# todo +``` + +`iam-group-with-assumable-roles-policy`: +```hcl +# todo +``` + +## IAM Best Practices + +AWS published [IAM Best Practices](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html) and this Terraform module was created to help with some of points listed there: + +### Create Individual IAM Users + +Use [iam-user module](https://github.com/terraform-aws-modules/terraform-aws-iam/tree/master/modules/iam-user) module to manage IAM users. + +### Use AWS Defined Policies to Assign Permissions Whenever Possible + +Use [iam-assumable-roles module](https://github.com/terraform-aws-modules/terraform-aws-iam/tree/master/modules/iam-assumable-roles) to create IAM roles with managed policies to support common tasks (admin, poweruser or readonly). + +### Use Groups to Assign Permissions to IAM Users + +Use [iam-group-with-assumable-roles-policy module](https://github.com/terraform-aws-modules/terraform-aws-iam/tree/master/modules/iam-group-with-assumable-roles-policy) to manage IAM groups of users who can assume roles. + +### Configure a Strong Password Policy for Your Users + +Use [iam-account module](https://github.com/terraform-aws-modules/terraform-aws-iam/tree/master/modules/iam-account) to set password policy for your IAM users. + +### Enable MFA for Privileged Users + +Terraform can't configure MFA for the user. It is only possible via [AWS Console](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_mfa.html) and [AWS CLI](https://docs.aws.amazon.com/cli/latest/reference/iam/enable-mfa-device.html). + +### Delegate by Using Roles Instead of by Sharing Credentials + +[iam-assumable-roles](https://github.com/terraform-aws-modules/terraform-aws-iam/tree/master/modules/iam-assumable-roles) and [iam-group-with-assumable-roles-policy](https://github.com/terraform-aws-modules/terraform-aws-iam/tree/master/modules/iam-group-with-assumable-roles-policy) modules provide complete set of functionality required for this. + +### Use Policy Conditions for Extra Security + +[iam-assumable-roles module](https://github.com/terraform-aws-modules/terraform-aws-iam/tree/master/modules/iam-assumable-roles) can be configured to require valid MFA token when different roles are assumed (for example, admin role requires MFA, but readonly - does not). + + +## Examples + +* [complete](https://github.com/terraform-aws-modules/terraform-aws-iam/tree/master/modules/complete) - Create all required resources to allow one group of users to assume privileged role, while another group of users can only assume readonly role. +* [iam-account](https://github.com/terraform-aws-modules/terraform-aws-iam/tree/master/modules/iam-account) - Set AWS account alias and password policy +* [iam-assumable-roles](https://github.com/terraform-aws-modules/terraform-aws-iam/tree/master/modules/iam-assumable-roles) - Create IAM roles which can be assumed from specified ARNs (AWS accounts, IAM users, etc) + + +## Authors + +Module managed by [Anton Babenko](https://github.com/antonbabenko). + +## License + +Apache 2 Licensed. See LICENSE for full details. diff --git a/examples/iam-account/README.md b/examples/iam-account/README.md index 507f0b6c..769d9859 100644 --- a/examples/iam-account/README.md +++ b/examples/iam-account/README.md @@ -1,12 +1,8 @@ -HTTP Security Group example -=========================== +# IAM account example -Configuration in this directory creates set of Security Group and Security Group Rules resources in various combination. +Configuration in this directory sets [AWS account alias](https://docs.aws.amazon.com/IAM/latest/UserGuide/console_account-alias.html) (also known as Console Account alias) and configures password policy. -Data sources are used to discover existing VPC resources (VPC and default security group). - -Usage -===== +# Usage To run this example you need to execute: @@ -16,4 +12,4 @@ $ terraform plan $ terraform apply ``` -Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. +Run `terraform destroy` when you don't need these resources. diff --git a/examples/iam-assumable-roles/README.md b/examples/iam-assumable-roles/README.md new file mode 100644 index 00000000..756bbb6f --- /dev/null +++ b/examples/iam-assumable-roles/README.md @@ -0,0 +1,15 @@ +# IAM assumable roles example + +Configuration in this directory creates several IAM roles which can be assumed from a defined list of [IAM ARNs](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html#identifiers-arns). + +# Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Run `terraform destroy` when you don't need these resources. diff --git a/examples/iam-assumable-roles/main.tf b/examples/iam-assumable-roles/main.tf new file mode 100644 index 00000000..b2ffcbc2 --- /dev/null +++ b/examples/iam-assumable-roles/main.tf @@ -0,0 +1,23 @@ +provider "aws" { + region = "eu-west-1" +} + +###################### +# IAM assumable roles +###################### +module "iam_assumable_roles" { + source = "../../modules/iam-assumable-roles" + + trusted_role_arns = [ + "arn:aws:iam::307990089504:root", + "arn:aws:iam::835367859851:user/anton", + ] + + create_admin_role = true + + create_poweruser_role = true + poweruser_role_name = "developer" + + create_readonly_role = true + readonly_role_requires_mfa = false +} diff --git a/examples/iam-assumable-roles/outputs.tf b/examples/iam-assumable-roles/outputs.tf new file mode 100644 index 00000000..fc6b7f87 --- /dev/null +++ b/examples/iam-assumable-roles/outputs.tf @@ -0,0 +1,62 @@ +# Admin +output "admin_iam_role_arn" { + description = "ARN of admin IAM role" + value = "${module.iam_assumable_roles.admin_iam_role_arn}" +} + +output "admin_iam_role_name" { + description = "Name of admin IAM role" + value = "${module.iam_assumable_roles.admin_iam_role_name}" +} + +output "admin_iam_role_requires_mfa" { + description = "Whether admin IAM role requires MFA" + value = "${module.iam_assumable_roles.admin_iam_role_requires_mfa}" +} + +output "admin_iam_role_path" { + description = "Path of admin IAM role" + value = "${module.iam_assumable_roles.admin_iam_role_path}" +} + +# Poweruser +output "poweruser_iam_role_arn" { + description = "ARN of poweruser IAM role" + value = "${module.iam_assumable_roles.poweruser_iam_role_arn}" +} + +output "poweruser_iam_role_name" { + description = "Name of poweruser IAM role" + value = "${module.iam_assumable_roles.poweruser_iam_role_name}" +} + +output "poweruser_iam_role_requires_mfa" { + description = "Whether poweruser IAM role requires MFA" + value = "${module.iam_assumable_roles.poweruser_iam_role_requires_mfa}" +} + +output "poweruser_iam_role_path" { + description = "Path of poweruser IAM role" + value = "${module.iam_assumable_roles.poweruser_iam_role_path}" +} + +# Readonly +output "readonly_iam_role_arn" { + description = "ARN of readonly IAM role" + value = "${module.iam_assumable_roles.readonly_iam_role_arn}" +} + +output "readonly_iam_role_name" { + description = "Name of readonly IAM role" + value = "${module.iam_assumable_roles.readonly_iam_role_name}" +} + +output "readonly_iam_role_path" { + description = "Path of readonly IAM role" + value = "${module.iam_assumable_roles.readonly_iam_role_path}" +} + +output "readonly_iam_role_requires_mfa" { + description = "Whether readonly IAM role requires MFA" + value = "${module.iam_assumable_roles.readonly_iam_role_requires_mfa}" +} diff --git a/modules/iam-assumable-roles/README.md b/modules/iam-assumable-roles/README.md new file mode 100644 index 00000000..308e402d --- /dev/null +++ b/modules/iam-assumable-roles/README.md @@ -0,0 +1,5 @@ +# iam-assumable-roles + +Creates predefined IAM roles (admin, poweruser and readonly) which can be assumed by trusted resources. + +Trusted resources can be any [IAM ARNs](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html#identifiers-arns) - typically, AWS accounts and users. diff --git a/modules/iam-assumable-roles/main.tf b/modules/iam-assumable-roles/main.tf new file mode 100644 index 00000000..173ebef6 --- /dev/null +++ b/modules/iam-assumable-roles/main.tf @@ -0,0 +1,76 @@ +data "aws_iam_policy_document" "assume_role" { + statement { + effect = "Allow" + + actions = ["sts:AssumeRole"] + + principals { + type = "AWS" + identifiers = ["${var.trusted_role_arns}"] + } + } +} + +data "aws_iam_policy_document" "assume_role_with_mfa" { + statement { + effect = "Allow" + + actions = ["sts:AssumeRole"] + + principals { + type = "AWS" + identifiers = ["${var.trusted_role_arns}"] + } + + condition { + test = "Bool" + variable = "aws:MultiFactorAuthPresent" + values = ["true"] + } + + condition { + test = "NumericLessThan" + variable = "aws:MultiFactorAuthAge" + values = ["${var.mfa_age}"] + } + } +} + +# Admin +resource "aws_iam_role" "admin" { + name = "${var.admin_role_name}" + path = "${var.admin_role_path}" + + assume_role_policy = "${var.admin_role_requires_mfa ? data.aws_iam_policy_document.assume_role_with_mfa.json : data.aws_iam_policy_document.assume_role.json}" +} + +resource "aws_iam_role_policy_attachment" "admin" { + role = "${aws_iam_role.admin.name}" + policy_arn = "${var.admin_role_policy_arn}" +} + +# Poweruser +resource "aws_iam_role_policy_attachment" "poweruser" { + role = "${aws_iam_role.poweruser.name}" + policy_arn = "${var.poweruser_role_policy_arn}" +} + +resource "aws_iam_role" "poweruser" { + name = "${var.poweruser_role_name}" + path = "${var.poweruser_role_path}" + + assume_role_policy = "${var.poweruser_role_requires_mfa ? data.aws_iam_policy_document.assume_role_with_mfa.json : data.aws_iam_policy_document.assume_role.json}" +} + +# Readonly +resource "aws_iam_role_policy_attachment" "readonly" { + role = "${aws_iam_role.readonly.name}" + policy_arn = "${var.readonly_role_policy_arn}" +} + +resource "aws_iam_role" "readonly" { + name = "${var.readonly_role_name}" + path = "${var.readonly_role_path}" + + assume_role_policy = "${var.readonly_role_requires_mfa ? data.aws_iam_policy_document.assume_role_with_mfa.json : data.aws_iam_policy_document.assume_role.json}" +} diff --git a/modules/iam-assumable-roles/outputs.tf b/modules/iam-assumable-roles/outputs.tf new file mode 100644 index 00000000..192672ae --- /dev/null +++ b/modules/iam-assumable-roles/outputs.tf @@ -0,0 +1,62 @@ +#Admin +output "admin_iam_role_arn" { + description = "ARN of admin IAM role" + value = "${element(concat(aws_iam_role.admin.*.arn, list()), 0)}" +} + +output "admin_iam_role_name" { + description = "Name of admin IAM role" + value = "${element(concat(aws_iam_role.admin.*.name, list()), 0)}" +} + +output "admin_iam_role_path" { + description = "Path of admin IAM role" + value = "${element(concat(aws_iam_role.admin.*.path, list()), 0)}" +} + +output "admin_iam_role_requires_mfa" { + description = "Whether admin IAM role requires MFA" + value = "${var.admin_role_requires_mfa}" +} + +# Poweruser +output "poweruser_iam_role_arn" { + description = "ARN of poweruser IAM role" + value = "${element(concat(aws_iam_role.poweruser.*.arn, list()), 0)}" +} + +output "poweruser_iam_role_name" { + description = "Name of poweruser IAM role" + value = "${element(concat(aws_iam_role.poweruser.*.name, list()), 0)}" +} + +output "poweruser_iam_role_path" { + description = "Path of poweruser IAM role" + value = "${element(concat(aws_iam_role.poweruser.*.path, list()), 0)}" +} + +output "poweruser_iam_role_requires_mfa" { + description = "Whether poweruser IAM role requires MFA" + value = "${var.poweruser_role_requires_mfa}" +} + +# Readonly +output "readonly_iam_role_arn" { + description = "ARN of readonly IAM role" + value = "${element(concat(aws_iam_role.readonly.*.arn, list()), 0)}" +} + +output "readonly_iam_role_name" { + description = "Name of readonly IAM role" + value = "${element(concat(aws_iam_role.readonly.*.name, list()), 0)}" +} + +output "readonly_iam_role_path" { + description = "Path of readonly IAM role" + value = "${element(concat(aws_iam_role.readonly.*.path, list()), 0)}" +} + +output "readonly_iam_role_requires_mfa" { + description = "Whether readonly IAM role requires MFA" + value = "${var.readonly_role_requires_mfa}" +} diff --git a/modules/iam-assumable-roles/variables.tf b/modules/iam-assumable-roles/variables.tf new file mode 100644 index 00000000..7129a2b3 --- /dev/null +++ b/modules/iam-assumable-roles/variables.tf @@ -0,0 +1,87 @@ +variable "trusted_role_arns" { + description = "ARNs of AWS entities who can assume these roles" + default = [] +} + +variable "mfa_age" { + description = "Max age of valid MFA (in seconds) for roles which require MFA" + default = 86400 +} + +# Admin +variable "create_admin_role" { + description = "Whether to create admin role" + default = false +} + +variable "admin_role_name" { + description = "IAM role with admin access" + default = "admin" +} + +variable "admin_role_path" { + description = "Path of admin IAM role" + default = "/" +} + +variable "admin_role_requires_mfa" { + description = "Whether admin role requires MFA" + default = true +} + +variable "admin_role_policy_arn" { + description = "Policy ARN to use for admin role" + default = "arn:aws:iam::aws:policy/AdministratorAccess" +} + +# Poweruser +variable "create_poweruser_role" { + description = "Whether to create poweruser role" + default = false +} + +variable "poweruser_role_name" { + description = "IAM role with poweruser access" + default = "poweruser" +} + +variable "poweruser_role_path" { + description = "Path of poweruser IAM role" + default = "/" +} + +variable "poweruser_role_requires_mfa" { + description = "Whether poweruser role requires MFA" + default = true +} + +variable "poweruser_role_policy_arn" { + description = "Policy ARN to use for admin role" + default = "arn:aws:iam::aws:policy/PowerUserAccess" +} + +# Readonly +variable "create_readonly_role" { + description = "Whether to create poweruser role" + default = false +} + +variable "readonly_role_name" { + description = "IAM role with readonly access" + default = "readonly" +} + +variable "readonly_role_path" { + description = "Path of poweruser IAM role" + default = "/" +} + +variable "readonly_role_requires_mfa" { + description = "Whether readonly role requires MFA" + default = true +} + +variable "readonly_role_policy_arn" { + description = "Policy ARN to use for admin role" + default = "arn:aws:iam::aws:policy/ReadOnlyAccess" +}