diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b0e03fc8..f7f40520 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/antonbabenko/pre-commit-terraform - rev: v1.58.0 + rev: v1.62.3 hooks: - id: terraform_fmt - id: terraform_validate @@ -23,6 +23,6 @@ repos: - '--args=--only=terraform_standard_module_structure' - '--args=--only=terraform_workspace_remote' - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.0.1 + rev: v4.1.0 hooks: - id: check-merge-conflict diff --git a/README.md b/README.md index 6ab5fdc2..c87ff874 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ ```hcl module "iam_account" { source = "terraform-aws-modules/iam/aws//modules/iam-account" - version = "~> 4.3" + version = "~> 4" account_alias = "awesome-company" @@ -26,7 +26,7 @@ module "iam_account" { ```hcl module "iam_assumable_role" { source = "terraform-aws-modules/iam/aws//modules/iam-assumable-role" - version = "~> 4.3" + version = "~> 4" trusted_role_arns = [ "arn:aws:iam::307990089504:root", @@ -51,7 +51,7 @@ module "iam_assumable_role" { ```hcl module "iam_assumable_role_with_oidc" { source = "terraform-aws-modules/iam/aws//modules/iam-assumable-role-with-oidc" - version = "~> 4.3" + version = "~> 4" create_role = true @@ -75,7 +75,7 @@ module "iam_assumable_role_with_oidc" { ```hcl module "iam_assumable_role_with_saml" { source = "terraform-aws-modules/iam/aws//modules/iam-assumable-role-with-saml" - version = "~> 4.3" + version = "~> 4" create_role = true @@ -99,7 +99,7 @@ module "iam_assumable_role_with_saml" { ```hcl module "iam_assumable_roles" { source = "terraform-aws-modules/iam/aws//modules/iam-assumable-roles" - version = "~> 4.3" + version = "~> 4" trusted_role_arns = [ "arn:aws:iam::307990089504:root", @@ -121,7 +121,7 @@ module "iam_assumable_roles" { ```hcl module "iam_assumable_roles_with_saml" { source = "terraform-aws-modules/iam/aws//modules/iam-assumable-roles-with-saml" - version = "~> 4.3" + version = "~> 4" create_admin_role = true @@ -139,7 +139,7 @@ module "iam_assumable_roles_with_saml" { ```hcl module "iam_user" { source = "terraform-aws-modules/iam/aws//modules/iam-user" - version = "~> 4.3" + version = "~> 4" name = "vasya.pupkin" force_destroy = true @@ -155,7 +155,7 @@ module "iam_user" { ```hcl module "iam_policy" { source = "terraform-aws-modules/iam/aws//modules/iam-policy" - version = "~> 4.3" + version = "~> 4" name = "example" path = "/" @@ -178,12 +178,27 @@ EOF } ``` +`iam-read-only-policy`: + +```hcl +module "iam_read_only_policy" { + source = "terraform-aws-modules/iam/aws//modules/iam-read-only-policy" + version = "~> 4" + + name = "example" + path = "/" + description = "My example read-only policy" + + allowed_services = ["rds", "dynamo", "health"] +} +``` + `iam-group-with-assumable-roles-policy`: ```hcl module "iam_group_with_assumable_roles_policy" { source = "terraform-aws-modules/iam/aws//modules/iam-group-with-assumable-roles-policy" - version = "~> 4.3" + version = "~> 4" name = "production-readonly" @@ -203,7 +218,7 @@ module "iam_group_with_assumable_roles_policy" { ```hcl module "iam_group_with_policies" { source = "terraform-aws-modules/iam/aws//modules/iam-group-with-policies" - version = "~> 4.3" + version = "~> 4" name = "superadmins" @@ -265,6 +280,8 @@ Terraform can't configure MFA for the user. It is only possible via [AWS Console Use [iam-policy module](https://github.com/terraform-aws-modules/terraform-aws-iam/tree/master/modules/iam-policy) module to manage IAM policy. +Use [iam-read-only-policy module](https://github.com/terraform-aws-modules/terraform-aws-iam/tree/master/modules/iam-read-only-policy) module to manage IAM read-only policies. + ## Examples - [iam-account](https://github.com/terraform-aws-modules/terraform-aws-iam/tree/master/examples/iam-account) - Set AWS account alias and password policy @@ -278,6 +295,7 @@ Use [iam-policy module](https://github.com/terraform-aws-modules/terraform-aws-i - [iam-group-complete](https://github.com/terraform-aws-modules/terraform-aws-iam/tree/master/examples/iam-group-complete) - IAM group with users who are allowed to assume IAM roles in another AWS account and have access to specified IAM policies - [iam-user](https://github.com/terraform-aws-modules/terraform-aws-iam/tree/master/examples/iam-user) - Add IAM user, login profile and access keys (with PGP enabled or disabled) - [iam-policy](https://github.com/terraform-aws-modules/terraform-aws-iam/tree/master/examples/iam-policy) - Create IAM policy +- [iam-read-only-policy](https://github.com/terraform-aws-modules/terraform-aws-iam/tree/master/examples/iam-read-only-policy) - Create IAM read-only policy ## Authors diff --git a/examples/iam-read-only-policy/README.md b/examples/iam-read-only-policy/README.md new file mode 100644 index 00000000..c998b610 --- /dev/null +++ b/examples/iam-read-only-policy/README.md @@ -0,0 +1,59 @@ +# IAM read-only policy example + +Configuration in this directory creates read-only IAM policy and attaches it to AWS SSO. + +# 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. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.12.6 | +| [aws](#requirement\_aws) | >= 2.23 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 2.23 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [read\_only\_iam\_policy](#module\_read\_only\_iam\_policy) | ../../modules/iam-read-only-policy | n/a | +| [read\_only\_iam\_policy\_doc](#module\_read\_only\_iam\_policy\_doc) | ../../modules/iam-read-only-policy | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_ssoadmin_permission_set.example](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssoadmin_permission_set) | resource | +| [aws_ssoadmin_permission_set_inline_policy.example](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssoadmin_permission_set_inline_policy) | resource | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [arn](#output\_arn) | The ARN assigned by AWS to this policy | +| [description](#output\_description) | The description of the policy | +| [id](#output\_id) | The policy ID | +| [name](#output\_name) | The name of the policy | +| [path](#output\_path) | The path of the policy in IAM | +| [policy](#output\_policy) | The policy document | + diff --git a/examples/iam-read-only-policy/main.tf b/examples/iam-read-only-policy/main.tf new file mode 100644 index 00000000..b0537829 --- /dev/null +++ b/examples/iam-read-only-policy/main.tf @@ -0,0 +1,66 @@ +provider "aws" { + region = "eu-west-1" +} + +locals { + allowed_services_compute = ["ec2", "ecr", "eks", "ecs", "lambda", "autoscaling", "elasticloadbalancing"] + allowed_services_networking = ["vpc", "route53", "route53domains", "route53resolver", "servicediscovery"] + allowed_services_storage = ["s3", "backup", "dynamo", "dms", "elasticfilesystem"] + allowed_services_databases = ["rds", "dynamo", "elasticache"] + allowed_services_management = ["cloudwatch", "events", "logs", "servicequotas", "ssm"] + allowed_services_analytics = ["es", "firehose", "kinesis", "kinesisanalytics", "redshift"] + allowed_services_application = ["ses", "sns", "sqs", "xray", "applicationinsights", "application-autoscaling"] + allowed_services_security = ["iam", "acm", "kms", "secretsmanager"] + + allowed_services = concat( + local.allowed_services_compute, + local.allowed_services_networking, + local.allowed_services_storage, + local.allowed_services_databases, + local.allowed_services_management, + local.allowed_services_analytics, + local.allowed_services_application, + local.allowed_services_security + ) +} + +module "read_only_iam_policy" { + source = "../../modules/iam-read-only-policy" + + name = "example" + path = "/" + description = "My read only example policy" + + allowed_services = local.allowed_services + + tags = { + PolicyDescription = "My read only example policy" + } +} + +module "read_only_iam_policy_doc" { + source = "../../modules/iam-read-only-policy" + + name = "only-doc-example" + path = "/" + description = "My read only example policy" + + create_policy = false + + allowed_services = local.allowed_services + + tags = { + PolicyDescription = "My read only example policy" + } +} + +resource "aws_ssoadmin_permission_set" "example" { + name = "Example" + instance_arn = "arn:aws:sso:::instance/example" +} + +resource "aws_ssoadmin_permission_set_inline_policy" "example" { + inline_policy = module.read_only_iam_policy_doc.policy_json + instance_arn = aws_ssoadmin_permission_set.example.instance_arn + permission_set_arn = aws_ssoadmin_permission_set.example.arn +} \ No newline at end of file diff --git a/examples/iam-read-only-policy/outputs.tf b/examples/iam-read-only-policy/outputs.tf new file mode 100644 index 00000000..9ca3c915 --- /dev/null +++ b/examples/iam-read-only-policy/outputs.tf @@ -0,0 +1,29 @@ +output "id" { + description = "The policy ID" + value = module.read_only_iam_policy.id +} + +output "arn" { + description = "The ARN assigned by AWS to this policy" + value = module.read_only_iam_policy.arn +} + +output "description" { + description = "The description of the policy" + value = module.read_only_iam_policy.description +} + +output "name" { + description = "The name of the policy" + value = module.read_only_iam_policy.name +} + +output "path" { + description = "The path of the policy in IAM" + value = module.read_only_iam_policy.path +} + +output "policy" { + description = "The policy document" + value = module.read_only_iam_policy.policy +} diff --git a/examples/iam-read-only-policy/variables.tf b/examples/iam-read-only-policy/variables.tf new file mode 100644 index 00000000..e69de29b diff --git a/examples/iam-read-only-policy/versions.tf b/examples/iam-read-only-policy/versions.tf new file mode 100644 index 00000000..fff6b757 --- /dev/null +++ b/examples/iam-read-only-policy/versions.tf @@ -0,0 +1,7 @@ +terraform { + required_version = ">= 0.12.6" + + required_providers { + aws = ">= 2.23" + } +} diff --git a/modules/iam-read-only-policy/README.md b/modules/iam-read-only-policy/README.md new file mode 100644 index 00000000..df90e199 --- /dev/null +++ b/modules/iam-read-only-policy/README.md @@ -0,0 +1,62 @@ +# iam-read-only-policy + +Creates IAM read-only policy for specified services. Default AWS read-only policies (arn:aws:iam::aws:policy/job-function/ViewOnlyAccess, arn:aws:iam::aws:policy/ReadOnlyAccess), being a one-size-fits-all type of policies, have a lot of things missing as well as something that you might not need. Also, AWS default policies are known for having [security issues](https://securityboulevard.com/2020/12/the-aws-managed-policies-trap/) +Thus this module is an attempt to build a better base for a customizable usable read-only policy. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.12.6 | +| [aws](#requirement\_aws) | >= 2.23 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 2.23 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_iam_policy.policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy_document.allowed_services](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.combined](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.console_services](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.logs_query](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.sts](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [additional\_policy\_json](#input\_additional\_policy\_json) | JSON policy document if you want to add custom actions | `string` | `"{}"` | no | +| [allow\_cloudwatch\_logs\_query](#input\_allow\_cloudwatch\_logs\_query) | Allows StartQuery/StopQuery/FilterLogEvents CloudWatch actions | `bool` | `true` | no | +| [allow\_predefined\_sts\_actions](#input\_allow\_predefined\_sts\_actions) | Allows GetCallerIdentity/GetSessionToken/GetAccessKeyInfo sts actions | `bool` | `true` | no | +| [allow\_web\_console\_services](#input\_allow\_web\_console\_services) | Allows List/Get/Describe/View actions for services used when browsing AWS console (e.g. resource-groups, tag, health services) | `bool` | `true` | no | +| [allowed\_services](#input\_allowed\_services) | List of services to allow Get/List/Describe/View options. Service name should be the same as corresponding service IAM prefix. See what it is for each service here https://docs.aws.amazon.com/service-authorization/latest/reference/reference_policies_actions-resources-contextkeys.html | `list(string)` | n/a | yes | +| [create\_policy](#input\_create\_policy) | Whether to create the IAM policy | `bool` | `true` | no | +| [description](#input\_description) | The description of the policy | `string` | `"IAM Policy"` | no | +| [name](#input\_name) | The name of the policy | `string` | `""` | no | +| [path](#input\_path) | The path of the policy in IAM | `string` | `"/"` | no | +| [tags](#input\_tags) | A map of tags to add to all resources. | `map(string)` | `{}` | no | +| [web\_console\_services](#input\_web\_console\_services) | List of web console services to allow | `list(string)` |
[
"resource-groups",
"tag",
"health"
]
| no | + +## Outputs + +| Name | Description | +|------|-------------| +| [arn](#output\_arn) | The ARN assigned by AWS to this policy | +| [description](#output\_description) | The description of the policy | +| [id](#output\_id) | The policy's ID | +| [name](#output\_name) | The name of the policy | +| [path](#output\_path) | The path of the policy in IAM | +| [policy](#output\_policy) | The policy document | +| [policy\_json](#output\_policy\_json) | Policy document as json. Useful if you need document but do not want to create IAM policy itself. For example for SSO Permission Set inline policies | + diff --git a/modules/iam-read-only-policy/main.tf b/modules/iam-read-only-policy/main.tf new file mode 100644 index 00000000..e417d49f --- /dev/null +++ b/modules/iam-read-only-policy/main.tf @@ -0,0 +1,90 @@ +resource "aws_iam_policy" "policy" { + count = var.create_policy ? 1 : 0 + + name = var.name + path = var.path + description = var.description + + policy = data.aws_iam_policy_document.combined.json + + tags = var.tags +} + +locals { + allowed_services = distinct(var.allowed_services) +} + +data "aws_iam_policy_document" "allowed_services" { + + dynamic "statement" { + for_each = toset(local.allowed_services) + content { + sid = replace(statement.value, "-", "") + + actions = [ + "${statement.value}:List*", + "${statement.value}:Get*", + "${statement.value}:Describe*", + "${statement.value}:View*", + ] + resources = ["*"] + } + } +} + +data "aws_iam_policy_document" "console_services" { + count = var.allow_web_console_services ? 1 : 0 + + dynamic "statement" { + for_each = toset(var.web_console_services) + content { + sid = replace(statement.value, "-", "") + + actions = [ + "${statement.value}:List*", + "${statement.value}:Get*", + "${statement.value}:Describe*", + "${statement.value}:View*", + ] + resources = ["*"] + } + } +} + +data "aws_iam_policy_document" "sts" { + count = var.allow_predefined_sts_actions ? 1 : 0 + + statement { + sid = "STS" + actions = [ + "sts:GetAccessKeyInfo", + "sts:GetCallerIdentity", + "sts:GetSessionToken", + ] + resources = ["*"] + } +} + +data "aws_iam_policy_document" "logs_query" { + count = var.allow_cloudwatch_logs_query ? 1 : 0 + + statement { + sid = "AllowLogsQuery" + actions = [ + "logs:StartQuery", + "logs:StopQuery", + "logs:FilterLogEvents" + ] + resources = ["*"] + } +} + +data "aws_iam_policy_document" "combined" { + source_policy_documents = concat( + [data.aws_iam_policy_document.allowed_services.json], + data.aws_iam_policy_document.console_services.*.json, + data.aws_iam_policy_document.sts.*.json, + data.aws_iam_policy_document.logs_query.*.json, + [var.additional_policy_json] + ) +} \ No newline at end of file diff --git a/modules/iam-read-only-policy/outputs.tf b/modules/iam-read-only-policy/outputs.tf new file mode 100644 index 00000000..08806e21 --- /dev/null +++ b/modules/iam-read-only-policy/outputs.tf @@ -0,0 +1,34 @@ +output "policy_json" { + description = "Policy document as json. Useful if you need document but do not want to create IAM policy itself. For example for SSO Permission Set inline policies" + value = data.aws_iam_policy_document.combined.json +} + +output "id" { + description = "The policy's ID" + value = element(concat(aws_iam_policy.policy.*.id, [""]), 0) +} + +output "arn" { + description = "The ARN assigned by AWS to this policy" + value = element(concat(aws_iam_policy.policy.*.arn, [""]), 0) +} + +output "description" { + description = "The description of the policy" + value = element(concat(aws_iam_policy.policy.*.description, [""]), 0) +} + +output "name" { + description = "The name of the policy" + value = element(concat(aws_iam_policy.policy.*.name, [""]), 0) +} + +output "path" { + description = "The path of the policy in IAM" + value = element(concat(aws_iam_policy.policy.*.path, [""]), 0) +} + +output "policy" { + description = "The policy document" + value = element(concat(aws_iam_policy.policy.*.policy, [""]), 0) +} \ No newline at end of file diff --git a/modules/iam-read-only-policy/variables.tf b/modules/iam-read-only-policy/variables.tf new file mode 100644 index 00000000..50aa63e4 --- /dev/null +++ b/modules/iam-read-only-policy/variables.tf @@ -0,0 +1,64 @@ +variable "create_policy" { + description = "Whether to create the IAM policy" + type = bool + default = true +} + +variable "name" { + description = "The name of the policy" + type = string + default = "" +} + +variable "path" { + description = "The path of the policy in IAM" + type = string + default = "/" +} + +variable "description" { + description = "The description of the policy" + type = string + default = "IAM Policy" +} + +variable "allowed_services" { + description = "List of services to allow Get/List/Describe/View options. Service name should be the same as corresponding service IAM prefix. See what it is for each service here https://docs.aws.amazon.com/service-authorization/latest/reference/reference_policies_actions-resources-contextkeys.html" + type = list(string) +} + +variable "additional_policy_json" { + description = "JSON policy document if you want to add custom actions" + type = string + default = "{}" +} + +variable "tags" { + description = "A map of tags to add to all resources." + type = map(string) + default = {} +} + +variable "allow_cloudwatch_logs_query" { + description = "Allows StartQuery/StopQuery/FilterLogEvents CloudWatch actions" + type = bool + default = true +} + +variable "allow_predefined_sts_actions" { + description = "Allows GetCallerIdentity/GetSessionToken/GetAccessKeyInfo sts actions" + type = bool + default = true +} + +variable "allow_web_console_services" { + description = "Allows List/Get/Describe/View actions for services used when browsing AWS console (e.g. resource-groups, tag, health services)" + type = bool + default = true +} + +variable "web_console_services" { + description = "List of web console services to allow" + type = list(string) + default = ["resource-groups", "tag", "health"] +} \ No newline at end of file diff --git a/modules/iam-read-only-policy/versions.tf b/modules/iam-read-only-policy/versions.tf new file mode 100644 index 00000000..fff6b757 --- /dev/null +++ b/modules/iam-read-only-policy/versions.tf @@ -0,0 +1,7 @@ +terraform { + required_version = ">= 0.12.6" + + required_providers { + aws = ">= 2.23" + } +}