Skip to content

Commit 8cba553

Browse files
authored
Merge pull request #4 from DefendableDesign/Slack-Integration
Slack Integration
2 parents d18ecec + 69d72a4 commit 8cba553

21 files changed

Lines changed: 508 additions & 48 deletions

File tree

main.tf

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ module "config" {
2727
module "remediation" {
2828
source = "./modules/aws_config/remediation_coordinator"
2929
enable_auto_response = "${var.enable_auto_response}"
30-
region = "${var.region}"
3130
}
3231

3332
module "rules" {
@@ -36,4 +35,15 @@ module "rules" {
3635
remediation_queue_url = "${module.remediation.remediation_queue_url}"
3736
remediation_queue_arn = "${module.remediation.remediation_queue_arn}"
3837
remediation_coordinator_lambda_arn = "${module.remediation.remediation_coordinator_lambda_arn}"
38+
notifier_enabled = "${var.slack_webhook_url == "" ? "false" : "true"}"
39+
}
40+
41+
module "notifier" {
42+
source = "./modules/notifier"
43+
slack_webhook_url = "${var.slack_webhook_url}"
44+
slack_channel = "${var.slack_channel}"
45+
kms_key_id = "${module.kms.kms_key_id}"
46+
kms_arn = "${module.kms.kms_arn}"
47+
monitoring_sns_arn = "${module.best_practice.sns_topic_arn}"
48+
config_sns_arn = "${module.config.sns_topic_arn}"
3949
}

modules/aws_config/remediation_coordinator/create_lambda.tf

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ POLICY
1919
}
2020

2121
data "aws_caller_identity" "current" {}
22+
data "aws_region" "current" {}
2223

2324
resource "aws_iam_role_policy" "p_remediation_coordinator" {
2425
name = "DD_Config_Policy_Remediation"
@@ -42,7 +43,7 @@ resource "aws_iam_role_policy" "p_remediation_coordinator" {
4243
"lambda:Get*"
4344
],
4445
"Effect": "Allow",
45-
"Resource": "arn:aws:lambda:${var.region}:${data.aws_caller_identity.current.account_id}:function:DD_Config_Lambda_*_Remediation"
46+
"Resource": "arn:aws:lambda:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:function:DD_Config_Lambda_*_Remediation"
4647
},
4748
{
4849
"Effect": "Allow",

modules/aws_config/remediation_coordinator/variables.tf

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,3 @@ variable "enable_auto_response" {
55
variable "temp_dir" {
66
default = "/tmp"
77
}
8-
9-
variable "region" {}

modules/aws_config/rules/main.tf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ module "restricted_ports_remediation" {
2222
remediation_queue_url = "${var.remediation_queue_url}"
2323
remediation_queue_arn = "${var.remediation_queue_arn}"
2424
remediation_coordinator_lambda_arn = "${var.remediation_coordinator_lambda_arn}"
25+
notifier_enabled = "${var.notifier_enabled}"
2526
}
2627

2728
module "s3_public_access" {
@@ -37,4 +38,5 @@ module "s3_public_access_remediation" {
3738
remediation_queue_url = "${var.remediation_queue_url}"
3839
remediation_queue_arn = "${var.remediation_queue_arn}"
3940
remediation_coordinator_lambda_arn = "${var.remediation_coordinator_lambda_arn}"
41+
notifier_enabled = "${var.notifier_enabled}"
4042
}

modules/aws_config/rules/restricted_ports_remediation/create_lambda.tf

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
data "aws_caller_identity" "current" {}
2+
data "aws_region" "current" {}
3+
14
resource "aws_iam_role" "r_remediation" {
2-
name = "DD_Config_Role_EC2_OpenPorts_Remediation"
5+
name = "DD_Config_Role_EC2_OpenPorts_Remediation"
36

4-
assume_role_policy = <<POLICY
7+
assume_role_policy = <<POLICY
58
{
69
"Version": "2012-10-17",
710
"Statement": [
@@ -19,10 +22,10 @@ POLICY
1922
}
2023

2124
resource "aws_iam_role_policy" "p_remediation" {
22-
name = "DD_Config_Policy_EC2_OpenPorts_Remediation"
23-
role = "${aws_iam_role.r_remediation.id}"
24-
25-
policy = <<POLICY
25+
name = "DD_Config_Policy_EC2_OpenPorts_Remediation"
26+
role = "${aws_iam_role.r_remediation.id}"
27+
28+
policy = <<POLICY
2629
{
2730
"Version": "2012-10-17",
2831
"Statement": [
@@ -51,26 +54,32 @@ resource "aws_iam_role_policy" "p_remediation" {
5154
"Resource": [
5255
"*"
5356
]
57+
},
58+
{
59+
"Action": [
60+
"lambda:InvokeFunction"
61+
],
62+
"Effect": "Allow",
63+
"Resource": "arn:aws:lambda:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:function:DD_Config_Lambda_Notifier"
5464
}
5565
]
5666
}
5767
POLICY
5868
}
5969

6070
resource "aws_lambda_function" "lf_remediation" {
61-
filename = "${data.archive_file.lambda_remediation.output_path}"
62-
function_name = "DD_Config_Lambda_EC2_OpenPorts_Remediation"
63-
role = "${aws_iam_role.r_remediation.arn}"
64-
handler = "dd_config_lambda_ec2_openports_remediation.lambda_handler"
65-
source_code_hash = "${base64sha256(file("${data.archive_file.lambda_remediation.output_path}"))}"
66-
runtime = "python2.7"
67-
timeout = "60"
68-
}
71+
filename = "${data.archive_file.lambda_remediation.output_path}"
72+
function_name = "DD_Config_Lambda_EC2_OpenPorts_Remediation"
73+
role = "${aws_iam_role.r_remediation.arn}"
74+
handler = "dd_config_lambda_ec2_openports_remediation.lambda_handler"
75+
source_code_hash = "${base64sha256(file("${data.archive_file.lambda_remediation.output_path}"))}"
76+
runtime = "python2.7"
77+
timeout = "60"
6978

70-
/*resource "aws_lambda_permission" "coordinator_invoke_permission" {
71-
statement_id = "DD_Config_LambdaPermission_EC2_OpenPorts_Remediation"
72-
action = "lambda:InvokeFunction"
73-
function_name = "${aws_lambda_function.lf_remediation.function_name}"
74-
principal = "lambda.amazonaws.com"
75-
source_arn = "${var.remediation_coordinator_lambda_arn}"
76-
}*/
79+
environment {
80+
variables = {
81+
notifierFnName = "DD_Config_Lambda_Notifier"
82+
notifierEnabled = "${var.notifier_enabled}"
83+
}
84+
}
85+
}

modules/aws_config/rules/restricted_ports_remediation/lambda_remediation/dd_config_lambda_ec2_openports_remediation.py

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
import json
1010
import boto3
1111
import botocore
12+
import os
13+
import datetime
14+
15+
NOTIFIER_ENABLED = os.environ["notifierEnabled"]
16+
NOTIFIER_FUNCTION = os.environ["notifierFnName"]
1217

1318
def remediate_violation(group_id, ip_permission):
1419
"""
@@ -46,9 +51,37 @@ def remediate_violation(group_id, ip_permission):
4651
"groupId": group_id,
4752
"accessControlPolicy": "Unexpected error: {0}".format(client_error)}
4853
print json.dumps(log_message)
49-
return False
50-
return True
54+
return False, log_message
55+
return True, log_message
5156

57+
def notify(log_message, source, account_id):
58+
"""
59+
Calls the Notifier Lambda function to notify slack that action has been taken.
60+
:param log_message: The log message string
61+
"""
62+
#pylint: disable=unused-variable
63+
print("Running notifier: {0}".format(NOTIFIER_FUNCTION))
64+
lambda_client = boto3.client('lambda')
65+
66+
payload = {
67+
"remediationSource" : source,
68+
"resourceId" : log_message["groupId"],
69+
"resourceType" : "AWS::EC2::SecurityGroup",
70+
"action" : log_message["action"],
71+
"actionCompleteTime" : datetime.datetime.utcnow().isoformat() + "+00:00",
72+
"awsAccountId" : account_id,
73+
"message" : "The following entry was removed from {0}:\n```\n{1}\n```".format(log_message["groupId"], json.dumps(log_message["ipPermission"], indent=4, sort_keys=True))
74+
}
75+
76+
payload_bytes = json.dumps(payload).encode('utf-8')
77+
78+
invoke_response = lambda_client.invoke(
79+
FunctionName=NOTIFIER_FUNCTION,
80+
InvocationType='Event',
81+
Payload=payload_bytes
82+
)
83+
84+
return True
5285

5386
def dequeue_message(sqs_url, receipt_handle):
5487
"""
@@ -115,9 +148,16 @@ def lambda_handler(event, context):
115148
ip_permission = body["violationDetails"]
116149

117150
success = False
118-
success = remediate_violation(group_id, ip_permission)
151+
result = remediate_violation(group_id, ip_permission)
152+
success = result[0]
153+
message = result[1]
119154

120155
if success:
121156
dequeue_message(sqs_url, receipt_handle)
157+
if NOTIFIER_ENABLED == "true":
158+
if message["action"] == "RevokeSecurityGroupIngress":
159+
fn_name = context.function_name
160+
account_id = context.invoked_function_arn.split(":")[4]
161+
notify(message, fn_name, account_id)
122162

123163
return True

modules/aws_config/rules/restricted_ports_remediation/variables.tf

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ variable "temp_dir" {
66
default = "/tmp"
77
}
88

9+
variable "notifier_enabled" {}
910
variable "remediation_queue_url" {}
1011
variable "remediation_queue_arn" {}
11-
12-
variable "remediation_coordinator_lambda_arn" {}
12+
variable "remediation_coordinator_lambda_arn" {}

modules/aws_config/rules/s3_public_access_remediation/create_lambda.tf

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
data "aws_caller_identity" "current" {}
2+
data "aws_region" "current" {}
3+
14
resource "aws_iam_role" "r_remediation" {
25
name = "DD_Config_Role_S3_PublicAccess_Remediation"
36

@@ -52,6 +55,13 @@ resource "aws_iam_role_policy" "p_remediation" {
5255
"Resource": [
5356
"*"
5457
]
58+
},
59+
{
60+
"Action": [
61+
"lambda:InvokeFunction"
62+
],
63+
"Effect": "Allow",
64+
"Resource": "arn:aws:lambda:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:function:DD_Config_Lambda_Notifier"
5565
}
5666
]
5767
}
@@ -66,13 +76,11 @@ resource "aws_lambda_function" "lf_remediation" {
6676
source_code_hash = "${base64sha256(file("${data.archive_file.lambda_remediation.output_path}"))}"
6777
runtime = "python2.7"
6878
timeout = "60"
69-
}
70-
71-
/*resource "aws_lambda_permission" "coordinator_invoke_permission" {
72-
statement_id = "DD_Config_LambdaPermission_S3_PublicAccess_Remediation"
73-
action = "lambda:InvokeFunction"
74-
function_name = "${aws_lambda_function.lf_remediation.function_name}"
75-
principal = "lambda.amazonaws.com"
76-
source_arn = "${var.remediation_coordinator_lambda_arn}"
77-
}*/
7879

80+
environment {
81+
variables = {
82+
notifierFnName = "DD_Config_Lambda_Notifier"
83+
notifierEnabled = "${var.notifier_enabled}"
84+
}
85+
}
86+
}

modules/aws_config/rules/s3_public_access_remediation/lambda_remediation/dd_config_lambda_s3_publicaccess_remediation.py

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
import json
1010
import boto3
1111
import botocore
12+
import os
13+
import datetime
14+
15+
NOTIFIER_ENABLED = os.environ["notifierEnabled"]
16+
NOTIFIER_FUNCTION = os.environ["notifierFnName"]
1217

1318
def remediate_violation_acl(bucket_name):
1419
"""
@@ -37,7 +42,6 @@ def remediate_violation_acl(bucket_name):
3742
)
3843
log_message = {"action": "PutBucketAcl", "bucketName": bucket_name, "accessControlPolicy": json.dumps(acp)}
3944
print json.dumps(log_message)
40-
return True
4145
except botocore.exceptions.ClientError as client_error:
4246
if client_error.response['Error']['Code'] == 'OperationAborted':
4347
log_message = {
@@ -53,7 +57,8 @@ def remediate_violation_acl(bucket_name):
5357
"bucketName": bucket_name,
5458
"accessControlPolicy": "Unexpected error: {0}".format(client_error)}
5559
print json.dumps(log_message)
56-
return False
60+
return False, log_message
61+
return True, log_message
5762

5863

5964
def remediate_violation_policy(bucket_name):
@@ -94,7 +99,47 @@ def remediate_violation_policy(bucket_name):
9499
"bucketName": bucket_name,
95100
"accessControlPolicy": "Unexpected error: {0}".format(client_error)}
96101
print json.dumps(log_message)
97-
return False
102+
return False, log_message
103+
return True, log_message
104+
105+
106+
def notify(log_message, source, account_id):
107+
"""
108+
Calls the Notifier Lambda function to notify slack that action has been taken.
109+
:param log_message: The log message string
110+
"""
111+
#pylint: disable=unused-variable
112+
lambda_client = boto3.client('lambda')
113+
114+
message = ""
115+
if log_message["action"] == "PutBucketPolicy":
116+
template = "A \"*\" Principal in an Allow policy statement was fixed by applying an updated bucket policy to {0}:\n```\n{1}\n```"
117+
bucket_policy = json.loads(log_message["bucketPolicy"])
118+
bucket_policy = json.dumps(bucket_policy, indent=4, sort_keys=True)
119+
message = template.format(log_message["bucketName"], bucket_policy)
120+
elif log_message["action"] == "PutBucketAcl":
121+
template = "An AllUsers or AllAuthenticatedUsers grant was fixed by applying an updated access control policy to {0}:\n```\n{1}\n```"
122+
acp = json.loads(log_message["accessControlPolicy"])
123+
acp = json.dumps(acp, indent=4, sort_keys=True)
124+
message = template.format(log_message["bucketName"], acp)
125+
126+
payload = {
127+
"remediationSource" : source,
128+
"resourceId" : log_message["bucketName"],
129+
"resourceType" : "AWS::S3::Bucket",
130+
"action" : log_message["action"],
131+
"actionCompleteTime" : datetime.datetime.utcnow().isoformat() + "+00:00",
132+
"awsAccountId" : account_id,
133+
"message" : message
134+
}
135+
136+
payload_bytes = json.dumps(payload).encode('utf-8')
137+
138+
invoke_response = lambda_client.invoke(
139+
FunctionName=NOTIFIER_FUNCTION,
140+
InvocationType='Event',
141+
Payload=payload_bytes
142+
)
98143
return True
99144

100145

@@ -126,11 +171,19 @@ def lambda_handler(event, context):
126171

127172
success = False
128173
if violation_type == "S3_BUCKET_PUBLIC_ACL":
129-
success = remediate_violation_acl(bucket_name)
174+
result = remediate_violation_acl(bucket_name)
130175
elif violation_type == "S3_BUCKET_PUBLIC_POLICY":
131-
success = remediate_violation_policy(bucket_name)
176+
result = remediate_violation_policy(bucket_name)
177+
178+
success = result[0]
179+
message = result[1]
132180

133181
if success:
134182
dequeue_message(sqs_url, receipt_handle)
135-
183+
if NOTIFIER_ENABLED == "true":
184+
if (message["action"] == "PutBucketPolicy" or message["action"] == "PutBucketAcl"):
185+
fn_name = context.function_name
186+
account_id = context.invoked_function_arn.split(":")[4]
187+
notify(message, fn_name, account_id)
188+
136189
return True

modules/aws_config/rules/s3_public_access_remediation/variables.tf

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ variable "temp_dir" {
66
default = "/tmp"
77
}
88

9+
variable "notifier_enabled" {}
910
variable "remediation_queue_url" {}
1011
variable "remediation_queue_arn" {}
11-
12-
variable "remediation_coordinator_lambda_arn" {}
12+
variable "remediation_coordinator_lambda_arn" {}

0 commit comments

Comments
 (0)