Skip to content

Commit dc3a40b

Browse files
committed
add permission notifier module
1 parent b1f6b83 commit dc3a40b

File tree

11 files changed

+505
-0
lines changed

11 files changed

+505
-0
lines changed

permission-alert/README.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# IAM Role Admin Privilege Monitor
2+
3+
This project provides an automated way to **monitor IAM roles for privilege escalation**. It leverages AWS EventBridge, Lambda, and SNS to detect when IAM role permissions change and sends an alert if administrative privileges are added.
4+
5+
---
6+
7+
## Overview
8+
9+
- **Input:** One or more IAM role names to monitor.
10+
- **Event Trigger:** AWS EventBridge rule that listens for `IAM Role Policy` changes.
11+
- **Detection:** A Lambda function (written in TypeScript) inspects the target role’s:
12+
- Inline policies
13+
- Attached custom policies
14+
- Attached managed policies
15+
- **Alerting:** If the role gains `AdministratorAccess` or wildcard permissions (`*` actions/resources), the Lambda publishes a message to the specified SNS topic.
16+
17+
This helps prevent **unauthorized privilege escalation** in AWS accounts.
18+
19+
---
20+
21+
## Architecture
22+
23+
1. **Terraform Module**
24+
- Creates EventBridge rule for IAM role policy changes
25+
- Provisions a Lambda function with detection logic
26+
- Grants Lambda necessary IAM permissions
27+
- Connects Lambda to SNS topic for alerts
28+
29+
2. **Lambda Function (TypeScript)**
30+
- Fetches IAM role policy details
31+
- Detects admin-like privileges
32+
- Publishes findings to SNS
33+
34+
---
35+
36+
## Project Structure
37+
38+
.
39+
├── main.tf # Root module entrypoint
40+
├── iam.tf # IAM permissions for Lambda
41+
├── lambda.tf # Lambda packaging and deployment
42+
├── cw_events.tf # CloudWatch / EventBridge rules
43+
├── outputs.tf # Module outputs
44+
├── variables.tf # Input variables
45+
├── versions.tf # Provider versions
46+
└── lambda/
47+
48+
---
49+
50+
## Inputs
51+
52+
| Variable | Type | Description |
53+
| --------------- | -------------- | --------------------------------------------------------------- |
54+
| `role_names` | `list(string)` | List of IAM role names to monitor (e.g. `["role_A", "role_B"]`) |
55+
| `sns_topic_arn` | `string` | ARN of an existing SNS topic to publish alerts |
56+
57+
---
58+
59+
## Outputs
60+
61+
| Output | Description |
62+
| ------------- | ---------------------------------------------------- |
63+
| `lambda_name` | Name of the monitoring Lambda function |
64+
| `rule_arn` | ARN of the EventBridge rule that triggers the Lambda |
65+
66+
---
67+
68+
## Usage
69+
70+
Example Terraform configuration:
71+
72+
```hcl
73+
module "iam_role_alerts" {
74+
source = "<path_to_this_repository_and_version>"
75+
sns_topic_arn = aws_sns_topic.role_policy_alerts.arn
76+
role_names = ["my-infra-apply-role", "my-infra-plan-role"]
77+
}
78+
```

permission-alert/cw_events.tf

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# EventBridge rule to react to any IAM changes that could elevate privileges
2+
# While this detects changes to any role, the Lambda will filter to just the target role
3+
resource "aws_cloudwatch_event_rule" "iam_changes" {
4+
name = "role-admin-detector-iam-events"
5+
description = "Trigger when IAM role/policy changes occur"
6+
7+
event_pattern = jsonencode({
8+
"source" : ["aws.iam"],
9+
"detail-type" : ["AWS API Call via CloudTrail"],
10+
"detail" : {
11+
"eventSource" : ["iam.amazonaws.com"],
12+
"eventName" : [
13+
# Attach/detach/inline policy changes for roles
14+
"AttachRolePolicy",
15+
"DetachRolePolicy",
16+
"PutRolePolicy",
17+
"DeleteRolePolicy",
18+
# Managed policy lifecycle that could affect effective perms
19+
"CreatePolicy",
20+
"CreatePolicyVersion",
21+
"SetDefaultPolicyVersion",
22+
"DeletePolicyVersion",
23+
"CreatePolicyVersion",
24+
# Permissions boundary or trust changes (paranoid coverage)
25+
"PutRolePermissionsBoundary",
26+
"DeleteRolePermissionsBoundary",
27+
"UpdateAssumeRolePolicy"
28+
]
29+
}
30+
})
31+
}
32+
33+
resource "aws_cloudwatch_event_target" "to_lambda" {
34+
rule = aws_cloudwatch_event_rule.iam_changes.name
35+
target_id = "role-admin-detector"
36+
arn = aws_lambda_function.detector.arn
37+
}

permission-alert/iam.tf

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
data "aws_iam_policy_document" "detector_lambda_assume" {
2+
statement {
3+
effect = "Allow"
4+
principals {
5+
type = "Service"
6+
identifiers = ["lambda.amazonaws.com"]
7+
}
8+
actions = ["sts:AssumeRole"]
9+
}
10+
}
11+
12+
resource "aws_iam_role" "detector_lambda" {
13+
name = "iam-admin-detector-role"
14+
assume_role_policy = data.aws_iam_policy_document.detector_lambda_assume.json
15+
}
16+
17+
# Permissions for:
18+
# - Reading IAM role policies (attached + inline)
19+
# - Publishing to SNS
20+
# - Writing CloudWatch Logs
21+
data "aws_iam_policy_document" "detector_permissions" {
22+
statement {
23+
sid = "IamRead"
24+
effect = "Allow"
25+
actions = [
26+
"iam:GetPolicy",
27+
"iam:GetPolicyVersion",
28+
"iam:GetRolePolicy",
29+
"iam:ListRolePolicies",
30+
"iam:ListAttachedRolePolicies"
31+
]
32+
resources = ["*"]
33+
}
34+
35+
statement {
36+
sid = "AllowPublishToTopic"
37+
effect = "Allow"
38+
actions = ["sns:Publish"]
39+
resources = [var.sns_topic_arn]
40+
}
41+
42+
statement {
43+
sid = "Logs"
44+
effect = "Allow"
45+
actions = [
46+
"logs:CreateLogGroup",
47+
"logs:CreateLogStream",
48+
"logs:PutLogEvents"
49+
]
50+
resources = ["arn:aws:logs:*:*:*"]
51+
}
52+
}
53+
54+
resource "aws_iam_policy" "detector_inline" {
55+
name = "iam-admin-detector-inline"
56+
policy = data.aws_iam_policy_document.detector_permissions.json
57+
}
58+
59+
resource "aws_iam_role_policy_attachment" "detector_inline_attach" {
60+
role = aws_iam_role.detector_lambda.name
61+
policy_arn = aws_iam_policy.detector_inline.arn
62+
}

permission-alert/lambda.tf

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Build the TypeScript Lambda locally using esbuild
2+
resource "null_resource" "build_lambda" {
3+
triggers = {
4+
src_hash = filesha256("${path.module}/lambda/src/index.ts")
5+
pkg_hash = filesha256("${path.module}/lambda/package.json")
6+
lock_hash = fileexists("${path.module}/lambda/package-lock.json") ? filesha256("${path.module}/lambda/package-lock.json") : ""
7+
tsconfig_hash = filesha256("${path.module}/lambda/tsconfig.json")
8+
}
9+
10+
provisioner "local-exec" {
11+
command = "cd ${path.module}/lambda && npm ci && npm run build"
12+
}
13+
}
14+
15+
data "archive_file" "detector_zip" {
16+
type = "zip"
17+
source_file = "${path.module}/lambda/dist/index.js"
18+
output_path = "${path.module}/lambda/detector.zip"
19+
20+
depends_on = [null_resource.build_lambda]
21+
}
22+
23+
resource "aws_lambda_function" "detector" {
24+
function_name = "role-admin-detector"
25+
role = aws_iam_role.detector_lambda.arn
26+
handler = "index.handler"
27+
runtime = "nodejs20.x"
28+
29+
filename = data.archive_file.detector_zip.output_path
30+
source_code_hash = data.archive_file.detector_zip.output_base64sha256
31+
32+
environment {
33+
variables = {
34+
SNS_TOPIC_ARN = var.sns_topic_arn
35+
TARGET_ROLES = join(",", var.role_names)
36+
}
37+
}
38+
39+
timeout = 30
40+
}
41+
42+
resource "aws_lambda_permission" "allow_events" {
43+
statement_id = "AllowExecutionFromEventBridge"
44+
action = "lambda:InvokeFunction"
45+
function_name = aws_lambda_function.detector.function_name
46+
principal = "events.amazonaws.com"
47+
source_arn = aws_cloudwatch_event_rule.iam_changes.arn
48+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "iam-admin-detector",
3+
"version": "1.0.0",
4+
"private": true,
5+
"type": "commonjs",
6+
"scripts": {
7+
"build": "esbuild src/index.ts --bundle --platform=node --target=node20 --format=cjs --outfile=dist/index.js"
8+
},
9+
"dependencies": {
10+
"@aws-sdk/client-iam": "^3.609.0",
11+
"@aws-sdk/client-sns": "^3.609.0"
12+
},
13+
"devDependencies": {
14+
"esbuild": "^0.23.0",
15+
"typescript": "^5.6.2",
16+
"@types/node": "^22.5.4",
17+
"@types/aws-lambda": "^8.10.137"
18+
}
19+
}

0 commit comments

Comments
 (0)