Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ This repository contains reusable infrastructure modules designed for enterprise
| `networking/` | `vpc` | AWS VPC with adaptive public and private subnets | v1.0.0 |
| `security/` | `acm_certificate` | AWS ACM public certificates with ordered domains, DNS validation, optional Route53, and optional wait | v1.0.0 |
| `security/` | `iam` | AWS IAM roles and policies | v1.0.0 |
| `security/` | `iam_policy` | Reusable customer-managed AWS IAM policies | v1.0.0 |
| `security/` | `kms` | AWS KMS keys (symmetric or asymmetric: signing, encryption, MAC, key agreement) | v1.0.0 |
| `security/` | `secrets-manager` | AWS Secrets Manager secrets | Planned |
| `stack/` | `terraform` | Ravion Terraform/OpenTofu stack workflows with git triggers and managed state (includes `rvn-stack` module definition) | v1.2.3 |
Expand Down
26 changes: 26 additions & 0 deletions security/iam_policy/.terraform.lock.hcl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

99 changes: 99 additions & 0 deletions security/iam_policy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# AWS IAM Policy Module

Creates one reusable customer-managed AWS IAM policy from structured statements or a complete JSON policy document.

## Usage

```hcl
module "application_policy" {
source = "git::https://github.com/flightcontrolhq/modules.git//security/iam_policy?ref=v1.0.0"

name = "application-s3-read"
description = "Allows the application to read objects from its bucket."

policy_statements = [{
sid = "ReadApplicationObjects"
effect = "Allow"
actions = ["s3:GetObject"]
resources = ["arn:aws:s3:::application-bucket/*"]
conditions = [{
test = "StringEquals"
variable = "aws:ResourceAccount"
values = ["123456789012"]
}]
}]

tags = {
Environment = "production"
}
}
```

Use `policy_json` when a complete policy document is more practical. When set, it overrides `policy_statements`.

```hcl
module "custom_policy" {
source = "git::https://github.com/flightcontrolhq/modules.git//security/iam_policy?ref=v1.0.0"

name = "custom-policy"
policy_json = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = ["logs:CreateLogStream", "logs:PutLogEvents"]
Resource = "arn:aws:logs:us-east-1:123456789012:log-group:/aws/example:*"
}]
})
}
```

## Structured Statements

Each structured statement must set exactly one of `actions` or `not_actions` and exactly one of `resources` or `not_resources`. `NotAction` and `NotResource` can create broad matches, especially with an `Allow` effect, so prefer positive action and resource lists unless an inverse statement is necessary.

```hcl
policy_statements = [{
sid = "DenyOutsideApprovedResources"
effect = "Deny"
actions = ["s3:*"]
not_resources = [
"arn:aws:s3:::approved-bucket",
"arn:aws:s3:::approved-bucket/*",
]
}]
```

## Requirements

| Name | Version |
|------|---------|
| opentofu/terraform | >= 1.10.0 |
| aws | >= 6.0 |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|----------|
| name | Customer-managed IAM policy name. | `string` | n/a | yes |
| description | Policy description. AWS does not allow updates after creation. | `string` | `null` | no |
| path | IAM path for the policy. | `string` | `"/"` | no |
| policy_statements | Structured IAM statements. Ignored when `policy_json` is set. | `list(object)` | `[]` | no |
| policy_json | Complete IAM policy JSON document. | `string` | `null` | no |
| region | AWS provider region. IAM resources are global. | `string` | `null` | no |
| tags | Tags to assign to the policy. | `map(string)` | `{}` | no |

At least one structured statement or a raw JSON policy document is required.

## Outputs

| Name | Description |
|------|-------------|
| policy_arn | ARN of the customer-managed IAM policy. |
| policy_id | ID of the customer-managed IAM policy. |
| policy_name | Name of the customer-managed IAM policy. |
| policy_path | Path of the customer-managed IAM policy. |
| attachment_count | Number of entities attached to the policy. |
| aws_account_id | AWS account ID where the policy is created. |
| region | AWS provider region used by the module. |

Attach `policy_arn` to roles through the `security/iam` module's `managed_policy_arns` input, or use it as that module's `permission_boundary_arn`.
30 changes: 30 additions & 0 deletions security/iam_policy/data.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
data "aws_caller_identity" "current" {}

data "aws_region" "current" {}

data "aws_iam_policy_document" "structured" {
count = var.policy_json == null && length(var.policy_statements) > 0 ? 1 : 0

dynamic "statement" {
for_each = var.policy_statements

content {
sid = statement.value.sid
effect = statement.value.effect
actions = statement.value.actions
not_actions = statement.value.not_actions
resources = statement.value.resources
not_resources = statement.value.not_resources

dynamic "condition" {
for_each = statement.value.conditions

content {
test = condition.value.test
variable = condition.value.variable
values = condition.value.values
}
}
}
}
}
14 changes: 14 additions & 0 deletions security/iam_policy/iam_policy.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
resource "aws_iam_policy" "this" {
name = var.name
description = var.description
path = var.path
policy = local.policy_document
tags = local.tags

lifecycle {
precondition {
condition = var.policy_json != null || length(var.policy_statements) > 0
error_message = "Provide at least one policy_statement or a policy_json document."
}
}
}
12 changes: 12 additions & 0 deletions security/iam_policy/locals.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
locals {
region = coalesce(var.region, data.aws_region.current.region)

default_tags = {
ManagedBy = "terraform"
Module = "security/iam_policy"
}

tags = merge(local.default_tags, var.tags)

policy_document = var.policy_json != null ? var.policy_json : one(data.aws_iam_policy_document.structured[*].json)
}
34 changes: 34 additions & 0 deletions security/iam_policy/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
output "policy_arn" {
description = "The ARN of the customer-managed IAM policy."
value = aws_iam_policy.this.arn
}

output "policy_id" {
description = "The ID of the customer-managed IAM policy."
value = aws_iam_policy.this.id
}

output "policy_name" {
description = "The name of the customer-managed IAM policy."
value = aws_iam_policy.this.name
}

output "policy_path" {
description = "The path of the customer-managed IAM policy."
value = aws_iam_policy.this.path
}

output "attachment_count" {
description = "The number of entities attached to the customer-managed IAM policy."
value = aws_iam_policy.this.attachment_count
}

output "aws_account_id" {
description = "The AWS account ID where the IAM policy is created."
value = data.aws_caller_identity.current.account_id
}

output "region" {
description = "The AWS region used to configure the provider."
value = local.region
}
Loading
Loading