diff --git a/.gitignore b/.gitignore index 11e5027..c696e99 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ -config/ -.devcontainer -.vscode \ No newline at end of file +#.vscode +config/ +.devcontainer +.vscode +#tf +.terraform +.terraform.lock.hcl \ No newline at end of file diff --git a/code/discordlambda/discordnote.py b/code/discordlambda/discordnote.py index 2076733..d097fc0 100644 --- a/code/discordlambda/discordnote.py +++ b/code/discordlambda/discordnote.py @@ -2,9 +2,12 @@ import json from discord_webhook import DiscordWebhook, DiscordEmbed import traceback +import boto3 + # Initialize logger logger = logging.getLogger() logger.setLevel(logging.INFO) + def lambda_handler(event, context): try: # Pulls from SQS event trigger, checks each result in the list, sets each necessary value within the elements read @@ -12,8 +15,25 @@ def lambda_handler(event, context): for record in records: message = json.loads(record['body']) for item in message: - # Alert Value so far between 'EC2_Public_Instance', + # Alert Value so far between 'EC2_Public_Instance', 'IAM Group Admin', 'IAM Admin User', & 'IAM Admin Role' alert = item['Alert'] + try: + # Grabs Secret from AWS Secrets Manager and sets it as a variable + secret_name = "tfc/sat/discordwebhook" + region_name = "us-east-1" + + # Create a Secrets Manager client + session = boto3.session.Session() + client = session.client( + service_name='secretsmanager', + region_name=region_name + ) + get_secret_value_response = client.get_secret_value( + SecretId=secret_name + ) + + secret = get_secret_value_response['SecretString'] + webhook = DiscordWebhook(url=secret) # EC2 Public Instance if alert == 'EC2_Public_Instance': # EC2 values set as variables @@ -26,11 +46,10 @@ def lambda_handler(event, context): try: # Sends the PublicEC2 Alert message formatted to be easily readable to Discord channel webhook integration, pings all users who can see the channel - webhook = DiscordWebhook(url="https://discordapp.com/api/webhooks/1147701063630196786/PVU9g477tn2u9ko0LZ5uTg4SUoQGqe_iSftGdhjZi1Szz5aIDDEew4soEPL80S3EYizy") embed = DiscordEmbed( title="Public EC2 Instance!", - description=f"Instance ID: {id}\nPublic IP: {public_ip}\nAttachment Time: {time_created}\nOpen Ports: {open_ports}\n\n@everyone", - allowed_mentions={"everyone"}, + description=f"Instance ID: {id}\nPublic IP: {public_ip}\nAttachment Time: {time_created}\nOpen Ports: {open_ports}\n\n@ravnsymphony", + allowed_mentions={"ravnsymphony"}, color=0x03b2f8 ) webhook.add_embed(embed) @@ -44,8 +63,7 @@ def lambda_handler(event, context): return { "statusCode": 500, "body": {"message": f"Error sending Discord message: {e}"} - } - + } # IAM Group Admin elif alert == 'Unauthorized_Admin_Group': # IAM values set as variables @@ -54,11 +72,10 @@ def lambda_handler(event, context): try: # Sends the PublicEC2 Alert message formatted to be easily readable to Discord channel webhook integration, pings all users who can see the channel - webhook = DiscordWebhook(url="https://discordapp.com/api/webhooks/1147701063630196786/PVU9g477tn2u9ko0LZ5uTg4SUoQGqe_iSftGdhjZi1Szz5aIDDEew4soEPL80S3EYizy") embed = DiscordEmbed( title="Unauthorized Admin!", - description=f"Group Name: {group}\nID: {group_id}\n\n@everyone", - allowed_mentions={"everyone"}, + description=f"Group Name: {group}\nID: {group_id}\n\n@ravnsymphony", + allowed_mentions={"ravnsymphony"}, color=0x03b2f8 ) webhook.add_embed(embed) @@ -81,11 +98,10 @@ def lambda_handler(event, context): try: # Sends the PublicEC2 Alert message formatted to be easily readable to Discord channel webhook integration, pings all users who can see the channel - webhook = DiscordWebhook(url="https://discordapp.com/api/webhooks/1147701063630196786/PVU9g477tn2u9ko0LZ5uTg4SUoQGqe_iSftGdhjZi1Szz5aIDDEew4soEPL80S3EYizy") embed = DiscordEmbed( title="Unauthorized Admin!", - description=f"User Name: {user}\nID: {user_id}\n\n@everyone", - allowed_mentions={"everyone"}, + description=f"User Name: {user}\nID: {user_id}\n\n@ravnsymphony", + allowed_mentions={"ravnsymphony"}, color=0x03b2f8 ) webhook.add_embed(embed) @@ -108,11 +124,10 @@ def lambda_handler(event, context): try: # Sends the PublicEC2 Alert message formatted to be easily readable to Discord channel webhook integration, pings all users who can see the channel - webhook = DiscordWebhook(url="https://discordapp.com/api/webhooks/1147701063630196786/PVU9g477tn2u9ko0LZ5uTg4SUoQGqe_iSftGdhjZi1Szz5aIDDEew4soEPL80S3EYizy") embed = DiscordEmbed( title="Unauthorized Admin!", - description=f"Role Name: {role}\nID: {role_id}\n\n@everyone", - allowed_mentions={"everyone"}, + description=f"Role Name: {role}\nID: {role_id}\n\n@ravnsymphony", + allowed_mentions={"ravnsymphony"}, color=0x03b2f8 ) webhook.add_embed(embed) @@ -127,6 +142,15 @@ def lambda_handler(event, context): "statusCode": 500, "body": {"message": f"Error sending Discord message: {e}"} } + except Exception as e: + traceback_message = traceback.format_exc() + logger.error(f"Error grabbing from Secret Manager: {e}") + logger.error(traceback_message) + logger.error(message) + return { + "statusCode": 500, + "body": {"message": f"Error grabbing from Secret Manager: {e}"} + } # Handles the errors for recieving the messages to Discord except Exception as e: traceback_message = traceback.format_exc() diff --git a/code/iamadminchecklambda/opiam-check.py b/code/iamadminchecklambda/opiam-check.py index 7b58b17..54a593d 100644 --- a/code/iamadminchecklambda/opiam-check.py +++ b/code/iamadminchecklambda/opiam-check.py @@ -1,111 +1,111 @@ -import logging -import os -import json -import boto3 -import traceback - -# Initialize logger -logger = logging.getLogger() -logger.setLevel(logging.INFO) - -def lambda_handler(event, context): - try: - policy_arn = 'arn:aws:iam::aws:policy/AdministratorAccess' - - # Get the list of all users attached to the policy - iam = boto3.client('iam') - policies = iam.list_entities_for_policy(PolicyArn=policy_arn) - - # Read the authorized IDs from the configuration file - with open('authorized_ids.json') as f: - authorized_ids = json.load(f) - - # Extract the authorized IDs for groups, users, and roles - id_authorized_groups = authorized_ids['AuthorizedGroups'] - id_authorized_users = authorized_ids['AuthorizedUsers'] - id_authorized_roles = authorized_ids['AuthorizedRoles'] - - # Check if the policy is used where it shouldn't be - Unauthorized_Admins = [] - - for group in policies['PolicyGroups']: - group_id = group['GroupId'] - if group_id not in id_authorized_groups: - Unauthorized_Admins.append(group) - - for user in policies['PolicyUsers']: - user_id = user['UserId'] - if user_id not in id_authorized_users: - Unauthorized_Admins.append(user) - - for role in policies['PolicyRoles']: - Role_id = role['RoleId'] - if Role_id not in id_authorized_roles: - Unauthorized_Admins.append(role) - - # Handles the errors for reading the IAM entities by policy - except Exception as e: - traceback_msg = traceback.format_exc() - logging.error(f"Error reading/listing IAM Entities: {str(e)}") - logging.error(traceback_msg) - return { - 'statusCode': 500, - 'body': {"message": f"Error reading/listing IAM Entities: {str(e)}"} - } - - # Check if the Unauthorized_Admins list is empty - if not Unauthorized_Admins: - # Return a status code 200 with a body 'No Results to publish' - return {'statusCode': 200, 'body': 'No Results to publish'} - else: - # Filter to only UserName and UserID - filtered_UnAuth = [] - for Unauthorized_Admin in Unauthorized_Admins: - if 'GroupName' in Unauthorized_Admin: - Group = { - "Alert": "Unauthorized_Admin_Group", - "Group": Unauthorized_Admin['GroupName'], - "ID": Unauthorized_Admin['GroupId'], - } - filtered_UnAuth.append(Group) - - elif 'UserName' in Unauthorized_Admin: - User = { - "Alert": "Unauthorized_Admin_User", - "User": Unauthorized_Admin['UserName'], - "ID": Unauthorized_Admin['UserId'], - } - filtered_UnAuth.append(User) - - elif 'RoleName' in Unauthorized_Admin: - Role = { - "Alert": "Unauthorized_Admin_Role", - "Role": Unauthorized_Admin['RoleName'], - "ID": Unauthorized_Admin['RoleId'], - } - filtered_UnAuth.append(Role) - - - # Publish the results to the SNS topic - sns = boto3.client('sns') - try: - pub_message=json.dumps(filtered_UnAuth, default=str, indent=4) - sns.publish( - TopicArn=os.getenv('SNS_TOPIC_ARN'), - Message=pub_message, - Subject='List of Unauthorized_Admins' - ) - - # Return a status code 200 with a body 'Results published to SNS' - return {'statusCode': 200, 'body': 'Results published to SNS'} - - # Handles the errors for pushing the results to the SNS topic - except Exception as e: - traceback_msg = traceback.format_exc() - logging.error(f"Error publishing to SNS Topic: {str(e)}") - logging.error(traceback_msg) - logging.error(pub_message) - return { - 'statusCode': 500, - 'body': {"message": f"Error publishing to SNS Topic: {str(e)}"} - } +import logging +import os +import json +import boto3 +import traceback + +# Initialize logger +logger = logging.getLogger() +logger.setLevel(logging.INFO) + +def lambda_handler(event, context): + try: + policy_arn = 'arn:aws:iam::aws:policy/AdministratorAccess' + + # Get the list of all users attached to the policy + iam = boto3.client('iam') + policies = iam.list_entities_for_policy(PolicyArn=policy_arn) + + # Read the authorized IDs from the configuration file + with open('authorized_ids.json') as f: + authorized_ids = json.load(f) + + # Extract the authorized IDs for groups, users, and roles + id_authorized_groups = authorized_ids['AuthorizedGroups'] + id_authorized_users = authorized_ids['AuthorizedUsers'] + id_authorized_roles = authorized_ids['AuthorizedRoles'] + + # Check if the policy is used where it shouldn't be + Unauthorized_Admins = [] + + for group in policies['PolicyGroups']: + group_id = group['GroupId'] + if group_id not in id_authorized_groups: + Unauthorized_Admins.append(group) + + for user in policies['PolicyUsers']: + user_id = user['UserId'] + if user_id not in id_authorized_users: + Unauthorized_Admins.append(user) + + for role in policies['PolicyRoles']: + Role_id = role['RoleId'] + if Role_id not in id_authorized_roles: + Unauthorized_Admins.append(role) + + # Handles the errors for reading the IAM entities by policy + except Exception as e: + traceback_msg = traceback.format_exc() + logging.error(f"Error reading/listing IAM Entities: {str(e)}") + logging.error(traceback_msg) + return { + 'statusCode': 500, + 'body': {"message": f"Error reading/listing IAM Entities: {str(e)}"} + } + + # Check if the Unauthorized_Admins list is empty + if not Unauthorized_Admins: + # Return a status code 200 with a body 'No Results to publish' + return {'statusCode': 200, 'body': 'No Results to publish'} + else: + # Filter to only UserName and UserID + filtered_UnAuth = [] + for Unauthorized_Admin in Unauthorized_Admins: + if 'GroupName' in Unauthorized_Admin: + Group = { + "Alert": "Unauthorized_Admin_Group", + "Group": Unauthorized_Admin['GroupName'], + "ID": Unauthorized_Admin['GroupId'], + } + filtered_UnAuth.append(Group) + + elif 'UserName' in Unauthorized_Admin: + User = { + "Alert": "Unauthorized_Admin_User", + "User": Unauthorized_Admin['UserName'], + "ID": Unauthorized_Admin['UserId'], + } + filtered_UnAuth.append(User) + + elif 'RoleName' in Unauthorized_Admin: + Role = { + "Alert": "Unauthorized_Admin_Role", + "Role": Unauthorized_Admin['RoleName'], + "ID": Unauthorized_Admin['RoleId'], + } + filtered_UnAuth.append(Role) + + + # Publish the results to the SNS topic + sns = boto3.client('sns') + try: + pub_message=json.dumps(filtered_UnAuth, default=str, indent=4) + sns.publish( + TopicArn=os.getenv('SNS_TOPIC_ARN'), + Message=pub_message, + Subject='List of Unauthorized_Admins' + ) + + # Return a status code 200 with a body 'Results published to SNS' + return {'statusCode': 200, 'body': 'Results published to SNS'} + + # Handles the errors for pushing the results to the SNS topic + except Exception as e: + traceback_msg = traceback.format_exc() + logging.error(f"Error publishing to SNS Topic: {str(e)}") + logging.error(traceback_msg) + logging.error(pub_message) + return { + 'statusCode': 500, + 'body': {"message": f"Error publishing to SNS Topic: {str(e)}"} + } diff --git a/docs/RoughDraft-Design.txt b/docs/RoughDraft-Design.txt index f70dce8..f187e9f 100644 --- a/docs/RoughDraft-Design.txt +++ b/docs/RoughDraft-Design.txt @@ -1,371 +1,371 @@ -###################################################################### -# PRIMARY LAMBDA # -###################################################################### -module "lambda" { - source = "terraform-aws-modules/lambda/aws" - version = "6.0.0" - - function_name = "lambda-sat-ec2" - description = "Checks for public facing ec2 instances" - handler = "index.lambda_handler" - runtime = var.py_runtime - source_code_hash = data.archive_file.lambda_archive_file.output_base64sha256 - source_path = data.archive_file.lambda_archive_file.output_path - role = aws_iam_role.lambda_role.arn - environment_variables = { - SNS_TOPIC_ARN = var.sns_topic_arn - anum = local.account_id - } - - tags = { - Name = "my-lambda1" - } -} - -data "archive_file" "lambda_archive_file" { - type = "zip" - source_file = "index.py" - output_path = "lambda_function_payload.zip" -} - -###################################################################### -# LAMBDA IAM # -###################################################################### - -resource "aws_iam_role" "lambda_role" { - name = "Lambda_role" - assume_role_policy = data.aws_iam_policy_document.main_lambda_policy.json -} - -resource "aws_iam_role_policy" "SNS_role-policy" { - name = "sns-topic-role-access" - role = aws_iam_role.lambda_role.id - policy = aws_iam_policy.sns_lambda_policy.policy -} - -data "aws_iam_policy_document" "main_lambda_policy" { - statement { - effect = "Allow" - principals { - type = "Service" - identifiers = ["lambda.amazonaws.com"] - } - actions = ["sts:AssumeRole"] - } -} - -resource "aws_iam_policy" "sns_lambda_policy" { - name = "sns-topic-access-policy" -policy { - policy = < Stage 1 & 2: Ideation and Requirement Collection - -## Meeting 1: -Goal setting! -### Made A Goal: - -**Build A Security Service that Checks for compliance or vulnerability issues!** - -**Discussed Rules:** - 1. Checks for publically accessible EC2 instances (Route table configs) - - 2. Checks for publically shared IAM resource policies - 3. Checks for overly permissive User/Role IAM policies - 4. All checks should be automated - 5. All checks should run at a set interval (like 3 am every night) - 6. All results should be stored somewhere for later review - -**(BONUS GOALS)** - - 7. Infrastructure should be documented in a Diagram as Code format (For a visual representation of the initial plans versus the final product) - - 8. Only use the AWS CLI and Terraform - 9. All results from the checks should be done timely (like under 5 minutes) - -**Asked two interview questions:** -1. Why should you avoid using the root account? What steps would you take to do so? - -2. What is the difference between a NACL and a Security Group? - -### Next Meeting: - Set a sub-goal: - - Plan out the way in which this will be done (Infrastructure, language, etc.) - -#
Stage 3: Design
- -- I put up a private Github repository for ease of collaboration and an eventual CI/CD automated workflow - -- I installed Diagram through pip on my windows machine for DaC development - -- Researched possibilities and avenues of development (CLI, EC2, ECS, Lambda, etc.) - -## Meeting 2: - -Laid out a plan and a Diagram: - -→ An Eventbridge Scheduler runs an event periodically - -→ That event will trigger a Lambda Function to run a python script that will (currently) check for an EC2 route table entry that is public - -→ Any results would be printed into a log file with a timestamp header and placed in an S3 bucket - -→ If the lambda function finds an issue or vulnerability it will log the event and send the results to SNS that will fans-out the messages to two SQS Queues, one for the S3 bucket and another to Discord for an alert message. - -As of now, the focus is to get the infrastructure up and ready for development and testing! - -### Next Meeting: - Set a sub-goal: - - Have the plan of how the infrastructure will look like flushed out and get started in prototyping/developing the structure. - -___ - -- Learned how to make DaC and made the first draft. - -- I began to write in my notes to make note of my progress and the process that it took to make this service. - -- Began to dive into Terraform development of my infrastructure and the python scripting needed to perform the check! -

+# Project SAT + +#
Stage 1 & 2: Ideation and Requirement Collection
+ +## Meeting 1: +Goal setting! +### Made A Goal: + +**Build A Security Service that Checks for compliance or vulnerability issues!** + +**Discussed Rules:** + 1. Checks for publicly accessible EC2 instances (Route table configs) + + 2. Checks for publicly shared IAM resource policies + 3. Checks for overly permissive User/Role IAM policies + 4. All checks should be automated + 5. All checks should run at a set interval (like 3 am every night) + 6. All results should be stored somewhere for later review + +**(BONUS GOALS)** + + 7. Infrastructure should be documented in a Diagram as Code format (For a visual representation of the initial plans versus the final product) + + 8. Only use the AWS CLI and Terraform + 9. All results from the checks should be done timely (like under 5 minutes) + +**Asked two interview questions:** +1. Why should you avoid using the root account? What steps would you take to do so? + +2. What is the difference between a NACL and a Security Group? + +### Next Meeting: + Set a sub-goal: + - Plan out the way in which this will be done (Infrastructure, language, etc.) + +#
Stage 3: Design
+ +- I put up a private Github repository for ease of collaboration and an eventual CI/CD automated workflow + +- I installed Diagram through pip on my windows machine for DaC development + +- Researched possibilities and avenues of development (CLI, EC2, ECS, Lambda, etc.) + +## Meeting 2: + +Laid out a plan and a Diagram: + +→ An Eventbridge Scheduler runs an event periodically + +→ That event will trigger a Lambda Function to run a python script that will (currently) check for an EC2 route table entry that is public + +→ Any results would be printed into a log file with a timestamp header and placed in an S3 bucket + +→ If the lambda function finds an issue or vulnerability it will log the event and send the results to SNS that will fans-out the messages to two SQS Queues, one for the S3 bucket and another to Discord for an alert message. + +As of now, the focus is to get the infrastructure up and ready for development and testing! + +### Next Meeting: + Set a sub-goal: + - Have the plan of how the infrastructure will look like flushed out and get started in prototyping/developing the structure. + +___ + +- Learned how to make DaC and made the first draft. + +- I began to write in my notes to make note of my progress and the process that it took to make this service. + +- Began to dive into Terraform development of my infrastructure and the python scripting needed to perform the check! +

1st Draft
\ No newline at end of file diff --git a/docs/Week2_Notes.md b/docs/Week2_Notes.md index dac1363..01493f4 100644 --- a/docs/Week2_Notes.md +++ b/docs/Week2_Notes.md @@ -1,16 +1,16 @@ -# Project SAT - -Update: - -- Developed and designed the terraform and python codes, undeployed - -- Created a main file for terraform, then split it up into several segments of code with relative organization (Main rough draft code is in RoughDraft-Designs.txt) - -- Updated draft, cannot link SQS to Discord or S3 without a lambda function to format it - -

-
2nd Draft
- -- Debugged and wrote any errors and attempts to fix in Debug-Error-Draft.md - +# Project SAT + +Update: + +- Developed and designed the terraform and python codes, un-deployed + +- Created a main file for terraform, then split it up into several segments of code with relative organization (Main rough draft code is in RoughDraft-Designs.txt) + +- Updated draft, cannot link SQS to Discord or S3 without a lambda function to format it + +

+
2nd Draft
+ +- Debugged and wrote any errors and attempts to fix in Debug-Error-Draft.md + - Completed the Design of the architecture and debugging, moving on to the inner-workings and python code \ No newline at end of file diff --git a/docs/Week3_Notes.md b/docs/Week3_Notes.md index 5f6720b..1f40d2e 100644 --- a/docs/Week3_Notes.md +++ b/docs/Week3_Notes.md @@ -1,33 +1,33 @@ -# Project SAT - -Update: Began working on IAM admin policy checker and more potential CSPM (Cloud Security Posture Management) checks - -

-
3rd Draft
- -## Meeting 3: - -→ Organize repo by directories - -→ Finish IAM Admin Check - -→ Minimize false positives by portscanning the public ec2 instance if it is actually publicly accessible - -→ Utilize the automatic requirements.txt installation of dependencies instead of lambda layers of the discord lambda module (check docs) - -→ POST ABOUT THE DaC, MENTION THE PROJECT - -### Next Meeting: - Set a sub-goal: Work through this stuff, get at least two done - - - Worked on getting these started: - - - I organized the Repo by file type and order of operation - - - Set up the IAM python code, based it on the EC2 code from before as a template - - - Placed potential ways to prevent false positives in the ec2 python code - - - Made the requirements.txt, still needs the pip install stuff (DOWN WITH THE LOCAL INSTALLATIUONS) - +# Project SAT + +Update: Began working on IAM admin policy checker and more potential CSPM (Cloud Security Posture Management) checks + +

+
3rd Draft
+ +## Meeting 3: + +→ Organize repo by directories + +→ Finish IAM Admin Check + +→ Minimize false positives by port scanning the public ec2 instance if it is actually publicly accessible + +→ Utilize the automatic requirements.txt installation of dependencies instead of lambda layers of the discord lambda module (check docs) + +→ POST ABOUT THE DaC, MENTION THE PROJECT + +### Next Meeting: + Set a sub-goal: Work through this stuff, get at least two done + + - Worked on getting these started: + + - I organized the Repo by file type and order of operation + + - Set up the IAM python code, based it on the EC2 code from before as a template + + - Placed potential ways to prevent false positives in the ec2 python code + + - Made the requirements.txt, still needs the pip install stuff (DOWN WITH THE LOCAL INSTALLATIONS) + - Posted about the DaC \ No newline at end of file diff --git a/tf/DiscordLambdaFunction.tf b/tf/DiscordLambdaFunction.tf index 4ceb7ce..14aa27e 100644 --- a/tf/DiscordLambdaFunction.tf +++ b/tf/DiscordLambdaFunction.tf @@ -22,8 +22,15 @@ module "lambda_Discord" { "sqs:GetQueueAttributes" ], "Resource": "${aws_sqs_queue.orders_to_notify.arn}" + }, + { + "Sid": "AllowLambdaDisSecretAccess", + "Effect": "Allow", + "Action": [ + "secretsmanager:GetSecretValue" + ], + "Resource": "arn:aws:secretsmanager:us-east-1:464004139021:secret:tfc/sat/discordwebhook-xD2JBX" } - ] } EOF diff --git a/tf/main.tf b/tf/main.tf index e0c9af1..672abc1 100644 --- a/tf/main.tf +++ b/tf/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = "~> 5.60.0" + version = "~> 5.79.0" } docker = { source = "kreuzwerker/docker" @@ -20,4 +20,12 @@ terraform { provider "aws" { region = var.reg + + default_tags { + tags = { + App = "Sec-SAT" + Env = "Dev" + IaC = "Terraform" + } + } } \ No newline at end of file