Skip to content

Commit 3bf3b17

Browse files
Release/automate remediation (#5)
* feat: apply principle of least privilege and only grant the minimum permissions require to operate neccessary operations * feat: upgrade node and terraform version * feat: allow the use of notification * feat: remediatete every last day of the month * feat: allow remediation in multiple regions and settings * feat: allow remediation fixes multiple times in a month * chore: update docs with added variables * chore: updates exmaple usage * Remediation gomboc-d3ebfda5-c25d-4122-8500-59825ed9bf27 (#6) Co-authored-by: gomboc-ai-community[bot] <215871000+gomboc-ai-community[bot]@users.noreply.github.com> * chore: add v2 video to docs and update docs * chore: update demo to picture and link --------- Co-authored-by: gomboc-ai-community[bot] <215871000+gomboc-ai-community[bot]@users.noreply.github.com>
1 parent ffad25b commit 3bf3b17

File tree

8 files changed

+177
-101
lines changed

8 files changed

+177
-101
lines changed

.tflint.hcl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ config {
44
}
55

66
plugin "aws" {
7-
enabled = true
8-
version = "0.30.0"
9-
source = "github.com/terraform-linters/tflint-ruleset-aws"
7+
enabled = true
8+
version = "0.30.0"
9+
source = "github.com/terraform-linters/tflint-ruleset-aws"
1010
}
1111

1212
rule "terraform_comment_syntax" {
13-
enabled = true
13+
enabled = true
1414
}
1515

1616
rule "terraform_naming_convention" {

README.md

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66

77
This Terraform module consists of the configuration for automating the remediation of AWS EC2 vulnerabilities using AWS Inspector findings. It provisions essential resources such as an SSM document, Lambda function, and CloudWatch event rules for automated vulnerability management.
88

9+
## Prerequisites
10+
11+
> **Important**
12+
>
13+
> The AWS Systems Manager (SSM) agent **must be installed and running** on all EC2 instances you wish to remediate. Without SSM, this module cannot trigger remediation actions on your instances.
14+
915
## Description
1016

1117
This Terraform module sets up an automated vulnerability remediation environment optimized for production use. By creating an SSM document to define the remediation steps, setting up a Lambda function to execute the remediation, and establishing CloudWatch event rules to trigger the process based on AWS Inspector findings, the module offers a straightforward approach to managing EC2 vulnerabilities on AWS.
@@ -47,7 +53,7 @@ module "remediation" {
4753
aws_region = "us-east-1"
4854
account_id = "2123232323"
4955
lambda_log_group = "/aws/lambda/vulne-soldier-compliance-remediate"
50-
lambda_zip = "./lambda.zip"
56+
path_to_lambda_zip = "./lambda.zip"
5157
remediation_options = {
5258
region = "us-east-1"
5359
reboot_option = "NoReboot"
@@ -58,6 +64,8 @@ module "remediation" {
5864
vulnerability_severities = ["CRITICAL, HIGH"]
5965
override_findings_for_target_instances_ids = []
6066
}
67+
remediation_schedule_days = ["15", "L"] # Schedule remediation on the 15th and last day of each month
68+
ssm_notification_topic_arn = null # Optional: Specify an SNS topic ARN to receive notifications for remediation events
6169
}
6270
6371
provider "aws" {
@@ -70,6 +78,16 @@ provider "aws" {
7078
On successful deployment, navigate to the AWS Systems Manager console and search for the SSM document created by the module (vulne-soldier-compliance-remediate-inspector-findings) or similar. You can trigger the remediation process by running the document on the affected EC2 instances. You can also create an AWS CloudWatch event rule to automate the process based on AWS Inspector findings.
7179

7280

81+
## What's New in v2
82+
83+
- Remediation is now **automated** using EventBridge rules, running by default with the `NoReboot` option for minimal disruption. You can update this option as needed in your configuration.
84+
85+
## Walkthrough Video
86+
87+
[![v2 Walkthrough Demo](assets/v2-walkthrough.png)](https://vimeo.com/1098910908?share=copy#t=3.684)
88+
89+
> Watch the [v2 walkthrough video](https://vimeo.com/1098910908?share=copy#t=3.684) for a step-by-step demonstration of setup and usage.
90+
7391
## Inputs
7492

7593
| Name | Description | Type | Default | Required |
@@ -79,14 +97,16 @@ On successful deployment, navigate to the AWS Systems Manager console and search
7997
| `aws_region` | AWS region where the resources will be created | `string` | n/a | yes |
8098
| `account_id` | AWS account ID | `string` | n/a | yes |
8199
| `lambda_log_group` | Name of the CloudWatch Log Group for the Lambda function | `string` | n/a | yes |
82-
| `lambda_zip` | File location of the lambda zip file for remediation | `string` | `lambda.zip` | yes |
83-
| `remediation_options` | Options for the remediation document | `object` | n/a | yes |
100+
| `path_to_lambda_zip` | File location of the lambda zip file for remediation | `string` | `lambda.zip` | yes |
101+
| `remediation_options` | Options for the remediation document | `object list` | n/a | yes |
84102
| `remediation_options.region` | The region to use | `string` | `us-east-1` | no |
85103
| `remediation_options.reboot_option` | Reboot option for patching | `string` | `NoReboot` | no |
86104
| `remediation_options.target_ec2_tag_name`| The tag name to filter EC2 instances | `string` | `AmazonECSManaged` | no |
87105
| `remediation_options.target_ec2_tag_value`| The tag value to filter EC2 instances | `string` | `true` | no |
88106
| `remediation_options.vulnerability_severities`| Comma separated list of vulnerability severities to filter findings | `string`| `"CRITICAL, HIGH"` | no |
89107
| `remediation_options.override_findings_for_target_instances_ids`| Comma separated list of instance IDs to override findings for target instances | `string`| `""` | no |
108+
| `remediation_schedule_days` | Days of the month to schedule remediation (e.g., ["15", "L"]) | `list(string)`| `["15", "L"]` | no |
109+
| `ssm_notification_topic_arn` | SNS topic ARN to receive notifications for remediation events (optional) | `string` | `null` | no |
90110

91111
## Outputs
92112

assets/v2-walkthrough.png

1.61 MB
Loading

examples/basic/main.tf

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
module "remediation" {
22
source = "iKnowJavaScript/vulne-soldier/aws"
3-
version = "1.0.2"
3+
version = "2.0.0"
44

5-
name = "vulne-soldier-compliance-remediate"
6-
environment = "dev"
7-
aws_region = "us-east-1"
8-
account_id = "111122223333"
9-
lambda_log_group = "/aws/lambda/vulne-soldier-compliance-remediate"
10-
lambda_zip = "../../lambda.zip"
11-
remediation_options = {
5+
name = "vulne-soldier-compliance-remediate"
6+
environment = "prod"
7+
aws_region = "us-east-1"
8+
account_id = "111122223333"
9+
lambda_log_group = "/aws/lambda/vulne-soldier-compliance-remediate"
10+
path_to_lambda_zip = "../../lambda.zip"
11+
remediation_options = [{
1212
region = "us-east-1"
1313
reboot_option = "NoReboot"
1414
target_ec2_tag_name = "AmazonECSManaged"
1515
target_ec2_tag_value = "true"
1616
vulnerability_severities = "CRITICAL, HIGH"
1717
override_findings_for_target_instances_ids = ""
18-
}
18+
}]
19+
remediation_schedule_days = ["15", "L"]
20+
ssn_notification_topic_arn = null
1921
}

examples/basic/terraform.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ terraform {
44
required_providers {
55
aws = {
66
source = "hashicorp/aws"
7-
version = "~> 4.0"
7+
version = "~> 5.0"
88
}
99
}
1010
}

main.tf

Lines changed: 91 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ provider "aws" {
55
locals {
66
function_name = "${var.name}-${var.environment}"
77
ssm_document_name = "${var.name}-inspector-findings-${var.environment}"
8-
lambda_zip = var.lambda_zip
8+
lambda_zip = var.path_to_lambda_zip
99
}
1010

1111
resource "aws_ssm_document" "remediation_document" {
@@ -20,32 +20,32 @@ resource "aws_ssm_document" "remediation_document" {
2020
"region": {
2121
"type": "String",
2222
"description": "(Required) The region to use.",
23-
"default": "${var.remediation_options.region}"
23+
"default": "${local.default_remediation_option.region}"
2424
},
2525
"rebootOption": {
2626
"type": "String",
2727
"description": "(Optional) Reboot option for patching. Allowed values: NoReboot, RebootIfNeeded, AlwaysReboot",
28-
"default": "${var.remediation_options.reboot_option}"
28+
"default": "${local.default_remediation_option.reboot_option}"
2929
},
3030
"targetEC2TagName": {
3131
"type": "String",
3232
"description": "The tag name to filter EC2 instances.",
33-
"default": "${var.remediation_options.target_ec2_tag_name}"
33+
"default": "${local.default_remediation_option.target_ec2_tag_name}"
3434
},
3535
"targetEC2TagValue": {
3636
"type": "String",
3737
"description": "The tag value to filter EC2 instances.",
38-
"default": "${var.remediation_options.target_ec2_tag_value}"
38+
"default": "${local.default_remediation_option.target_ec2_tag_value}"
3939
},
4040
"vulnerabilitySeverities": {
4141
"type": "String",
4242
"description": "(Optional) Comma separated list of vulnerability severities to filter findings. Allowed values are comma separated list of : CRITICAL, HIGH, MEDIUM, LOW, INFORMATIONAL",
43-
"default": "${var.remediation_options.vulnerability_severities}"
43+
"default": "${local.default_remediation_option.vulnerability_severities}"
4444
},
4545
"overrideFindingsForTargetInstancesIDs": {
4646
"type": "String",
4747
"description": "(Optional) Comma separated list of instance IDs to override findings for target instances. If not provided, all matched findings will be remediated. Values are in comma separated list of instance IDs.",
48-
"default": "${var.remediation_options.override_findings_for_target_instances_ids}"
48+
"default": "${local.default_remediation_option.override_findings_for_target_instances_ids}"
4949
}
5050
},
5151
"mainSteps": [
@@ -62,34 +62,62 @@ resource "aws_ssm_document" "remediation_document" {
6262
DOC
6363
}
6464

65-
# Set up an EventBridge rule that triggers on AWS Inspector findings.
66-
resource "aws_cloudwatch_event_rule" "inspector_findings" {
67-
name = "manual-inspector-findings-rule"
68-
description = "Triggers on AWS Inspector findings."
69-
70-
event_pattern = jsonencode({
71-
source = ["aws.inspector"],
72-
"detail-type" = ["Inspector Finding"],
73-
detail = {
74-
severity = ["High", "Critical", "MEDIUM", "LOW", "INFORMATIONAL"]
75-
}
76-
})
65+
locals {
66+
remediation_options_count = length(var.remediation_options)
67+
default_remediation_option = var.remediation_options[0]
68+
remediation_schedule_crons = [
69+
for day in var.remediation_schedule_days :
70+
"cron(0 2 ${day} * ? *)"
71+
]
72+
remediation_target_matrix = flatten([
73+
for schedule_idx, schedule in local.remediation_schedule_crons : [
74+
for opt_idx, opt in var.remediation_options : {
75+
schedule_idx = schedule_idx
76+
schedule = schedule
77+
opt_idx = opt_idx
78+
opt = opt
79+
}
80+
]
81+
])
7782
}
7883

79-
resource "aws_cloudwatch_event_target" "ssm_remediation" {
80-
rule = aws_cloudwatch_event_rule.inspector_findings.name
81-
target_id = "SSMVulneRemediationTarget"
82-
arn = aws_ssm_document.remediation_document.arn
84+
resource "aws_cloudwatch_event_rule" "inspector_findings_schedule" {
85+
count = length(local.remediation_schedule_crons)
86+
name = "${var.name}-rule-${count.index}-${var.environment}"
87+
description = "Triggers SSM remediation document on day ${var.remediation_schedule_days[count.index]} of each month."
88+
schedule_expression = local.remediation_schedule_crons[count.index]
8389

84-
run_command_targets {
85-
key = "tag:${var.remediation_options.target_ec2_tag_name}"
86-
values = [var.remediation_options.target_ec2_tag_value]
90+
tags = {
91+
Environment = var.environment
92+
Name = "${var.name}-rule-${var.environment}-${count.index}"
8793
}
94+
}
8895

89-
96+
resource "aws_cloudwatch_event_target" "ssm_remediation_scheduled" {
97+
for_each = {
98+
for item in local.remediation_target_matrix :
99+
"${item.schedule_idx}-${item.opt_idx}" => item
100+
}
101+
rule = aws_cloudwatch_event_rule.inspector_findings_schedule[each.value.schedule_idx].name
102+
target_id = "SSMVulneRemediationTarget-${each.value.opt_idx}-${each.value.schedule_idx}-${each.value.opt.region}"
103+
arn = aws_ssm_document.remediation_document.arn
104+
input = jsonencode({
105+
region = each.value.opt.region,
106+
rebootOption = each.value.opt.reboot_option,
107+
targetEC2TagName = each.value.opt.target_ec2_tag_name,
108+
targetEC2TagValue = each.value.opt.target_ec2_tag_value,
109+
vulnerabilitySeverities = each.value.opt.vulnerability_severities,
110+
overrideFindingsForTargetInstancesIDs = each.value.opt.override_findings_for_target_instances_ids,
111+
})
90112
role_arn = aws_iam_role.ssm_role.arn
91113
}
92114

115+
resource "aws_cloudwatch_event_target" "sns_inspector_alert_scheduled" {
116+
count = var.ssn_notification_topic_arn != null ? length(local.remediation_schedule_crons) : 0
117+
rule = aws_cloudwatch_event_rule.inspector_findings_schedule[count.index].name
118+
target_id = "InspectorCriticalHighAlertsSNS-${count.index}-${var.environment}"
119+
arn = var.ssn_notification_topic_arn
120+
}
93121

94122
resource "aws_iam_role" "ssm_role" {
95123
name = "SSMVulneAutomationRole"
@@ -106,12 +134,16 @@ resource "aws_iam_role" "ssm_role" {
106134
}
107135
]
108136
})
137+
}
138+
139+
resource "aws_iam_role_policy" "ssm_document_execution" {
140+
name = "SSMDocumentExecution"
141+
role = aws_iam_role.ssm_role.id
109142

110-
inline_policy {
111-
name = "SSMDocumentExecution"
112-
policy = jsonencode({
113-
Version = "2012-10-17",
114-
Statement = [
143+
policy = jsonencode({
144+
Version = "2012-10-17",
145+
Statement = concat(
146+
[
115147
{
116148
Action = [
117149
"ssm:StartAutomationExecution",
@@ -120,12 +152,18 @@ resource "aws_iam_role" "ssm_role" {
120152
Effect = "Allow",
121153
Resource = "arn:aws:ssm:*:*:document/${local.ssm_document_name}*"
122154
}
123-
]
124-
})
125-
}
155+
],
156+
var.ssn_notification_topic_arn != null ? [
157+
{
158+
Effect = "Allow",
159+
Action = "SNS:Publish",
160+
Resource = var.ssn_notification_topic_arn
161+
}
162+
] : []
163+
)
164+
})
126165
}
127166

128-
129167
resource "aws_iam_role" "lambda_execution_role" {
130168
name = "compliance-vulne-remediate_lambda_execution_role"
131169

@@ -154,16 +192,16 @@ resource "aws_iam_role_policy" "lambda_policy" {
154192
"logs:PutLogEvents"
155193
],
156194
Effect = "Allow",
157-
Resource = "arn:aws:logs:*:*:*"
195+
Resource = "arn:aws:logs:*:${var.account_id}:*"
158196
},
159197
{
160-
"Effect" : "Allow",
161-
"Action" : [
198+
Effect = "Allow",
199+
Action = [
162200
"ssm:StartAutomationExecution",
163201
"ssm:DescribeAutomationExecutions",
164202
"ssm:GetAutomationExecution"
165203
],
166-
"Resource" : "*"
204+
Resource = "arn:aws:ssm:*:${var.account_id}:automation-definition/*"
167205
},
168206
{
169207
"Effect" : "Allow",
@@ -173,11 +211,14 @@ resource "aws_iam_role_policy" "lambda_policy" {
173211
"Resource" : "*"
174212
},
175213
{
176-
"Effect" : "Allow",
177-
"Action" : [
214+
Effect = "Allow",
215+
Action = [
178216
"ssm:SendCommand"
179217
],
180-
"Resource" : "*"
218+
Resource = [
219+
"*",
220+
"arn:aws:ec2:*:${var.account_id}:instance/*"
221+
]
181222
},
182223
{
183224
"Effect" : "Allow",
@@ -186,15 +227,15 @@ resource "aws_iam_role_policy" "lambda_policy" {
186227
"inspector2:ListFindings",
187228
"inspector2:updateFindings"
188229
],
189-
"Resource" : "*"
230+
"Resource" : "arn:aws:inspector2:*:${var.account_id}:*"
190231
},
191232
{
192233
"Effect" : "Allow",
193234
"Action" : [
194235
"inspector:DescribeFindings",
195236
"inspector:ListFindings"
196237
],
197-
"Resource" : "*"
238+
"Resource" : "arn:aws:inspector:*:${var.account_id}:*"
198239
}],
199240
})
200241
}
@@ -205,7 +246,7 @@ resource "aws_lambda_function" "inspector_remediation" {
205246
function_name = local.function_name
206247
role = aws_iam_role.lambda_execution_role.arn
207248
handler = "index.handler"
208-
runtime = "nodejs18.x"
249+
runtime = "nodejs20.x"
209250
source_code_hash = filebase64sha256(local.lambda_zip)
210251

211252
timeout = 300
@@ -220,20 +261,7 @@ resource "aws_lambda_function" "inspector_remediation" {
220261
tags = {
221262
Environment = var.environment
222263
}
223-
}
224-
225-
226-
resource "aws_cloudwatch_event_target" "lambda" {
227-
rule = aws_cloudwatch_event_rule.inspector_findings.name
228-
arn = aws_lambda_function.inspector_remediation.arn
229-
}
230-
231-
resource "aws_lambda_permission" "allow_cloudwatch_to_call_lambda" {
232-
statement_id = "AllowExecutionFromCloudWatch"
233-
action = "lambda:InvokeFunction"
234-
function_name = aws_lambda_function.inspector_remediation.function_name
235-
principal = "events.amazonaws.com"
236-
source_arn = aws_cloudwatch_event_rule.inspector_findings.arn
237-
}
238-
239-
264+
tracing_config {
265+
mode = "Active"
266+
}
267+
}

0 commit comments

Comments
 (0)