commit 5db67720fddc7ab287413962f1d3ef15e572f7ac Author: EC2 Default User Date: Mon Aug 5 02:30:34 2024 +0000 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a635177 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.vscode +__pycache__ +venv +env + +bp.json diff --git a/bp-base.json b/bp-base.json new file mode 100644 index 0000000..8c63bfa --- /dev/null +++ b/bp-base.json @@ -0,0 +1,628 @@ +{ + "ALB": { + "enabled": true, + "rules": { + "alb-http-drop-invalid-header-enabled": { + "enabled": true, + "level": 2 + }, + "alb-waf-enabled": { + "enabled": true, + "level": 2 + }, + "elb-cross-zone-load-balancing-enabled": { + "enabled": true, + "level": 2 + }, + "elb-deletion-protection-enabled": { + "enabled": true, + "level": 1 + }, + "elb-logging-enabled": { + "enabled": true, + "level": 2 + } + } + }, + "API GW": { + "enabled": true, + "rules": { + "api-gwv2-access-logs-enabled": { + "enabled": true, + "level": 2 + }, + "api-gwv2-authorization-type-configured": { + "enabled": true, + "level": 1 + }, + "api-gw-associated-with-waf": { + "enabled": true, + "level": 2 + }, + "api-gw-cache-enabled-and-encrypted": { + "enabled": true, + "level": 2 + }, + "api-gw-execution-logging-enabled": { + "enabled": true, + "level": 2 + }, + "api-gw-xray-enabled": { + "enabled": true, + "level": 1 + } + } + }, + "RDS": { + "enabled": true, + "rules": { + "aurora-last-backup-recovery-point-created": { + "enabled": true, + "level": 2 + }, + "aurora-mysql-backtracking-enabled": { + "enabled": true, + "level": 2 + }, + "db-instance-backup-enabled": { + "enabled": true, + "level": 2 + }, + "rds-cluster-auto-minor-version-upgrade-enable": { + "enabled": true, + "level": 2 + }, + "rds-cluster-default-admin-check": { + "enabled": true, + "level": 2 + }, + "rds-cluster-deletion-protection-enabled": { + "enabled": true, + "level": 1 + }, + "rds-cluster-encrypted-at-rest": { + "enabled": true, + "level": 2 + }, + "rds-cluster-iam-authentication-enabled": { + "enabled": true, + "level": 2 + }, + "rds-cluster-multi-az-enabled": { + "enabled": true, + "level": 2 + }, + "rds-db-security-group-not-allowed": { + "enabled": true, + "level": 2 + }, + "rds-enhanced-monitoring-enabled": { + "enabled": true, + "level": 2 + }, + "rds-instance-deletion-protection-enabled": { + "enabled": true, + "level": 1 + }, + "rds-instance-public-access-check": { + "enabled": true, + "level": 2 + }, + "rds-logging-enabled": { + "enabled": true, + "level": 2 + }, + "rds-snapshot-encrypted": { + "enabled": true, + "level": 2 + } + } + }, + "ASG": { + "enabled": true, + "rules": { + "autoscaling-group-elb-healthcheck-required": { + "enabled": true, + "level": 2 + }, + "autoscaling-multiple-az": { + "enabled": true, + "level": 2 + } + } + }, + "EC2": { + "enabled": true, + "rules": { + "autoscaling-launch-template": { + "enabled": true, + "level": 2 + }, + "ec2-ebs-encryption-by-default": { + "enabled": true, + "level": 2 + }, + "ec2-imdsv2-check": { + "enabled": true, + "level": 2 + }, + "ec2-instance-detailed-monitoring-enabled": { + "enabled": true, + "level": 2 + }, + "ec2-instance-managed-by-systems-manager": { + "enabled": true, + "level": 2 + }, + "ec2-instance-profile-attached": { + "enabled": true, + "level": 2 + }, + "ec2-no-amazon-key-pair": { + "enabled": true, + "level": 1 + }, + "ec2-stopped-instance": { + "enabled": true, + "level": 2 + }, + "ec2-token-hop-limit-check": { + "enabled": true, + "level": 2 + } + } + }, + "CloudFront": { + "enabled": true, + "rules": { + "cloudfront-accesslogs-enabled": { + "enabled": true, + "level": 2 + }, + "cloudfront-associated-with-waf": { + "enabled": true, + "level": 2 + }, + "cloudfront-default-root-object-configured": { + "enabled": true, + "level": 2 + }, + "cloudfront-no-deprecated-ssl-protocols": { + "enabled": true, + "level": 2 + }, + "cloudfront-s3-origin-access-control-enabled": { + "enabled": true, + "level": 2 + }, + "cloudfront-viewer-policy-https": { + "enabled": true, + "level": 2 + } + } + }, + "KMS": { + "enabled": true, + "rules": { + "cmk-backing-key-rotation-enabled": { + "enabled": true, + "level": 2 + } + } + }, + "CodeSeries": { + "enabled": true, + "rules": { + "codebuild-project-environment-privileged-check": { + "enabled": true, + "level": 1 + }, + "codebuild-project-logging-enabled": { + "enabled": true, + "level": 2 + }, + "codedeploy-auto-rollback-monitor-enabled": { + "enabled": true, + "level": 2 + } + } + }, + "CloudWatch": { + "enabled": true, + "rules": { + "cw-loggroup-retention-period-check": { + "enabled": true, + "level": 2 + }, + "cloudwatch-alarm-settings-check": { + "enabled": true, + "level": 2 + } + } + }, + "DocDB": { + "enabled": true, + "rules": { + "docdb-cluster-audit-logging-enabled": { + "enabled": true, + "level": 2 + }, + "docdb-cluster-backup-retention-check": { + "enabled": true, + "level": 2 + }, + "docdb-cluster-deletion-protection-enabled": { + "enabled": true, + "level": 1 + }, + "docdb-cluster-encrypted": { + "enabled": true, + "level": 2 + } + } + }, + "DynamoDB": { + "enabled": true, + "rules": { + "dynamodb-autoscaling-enabled": { + "enabled": true, + "level": 2 + }, + "dynamodb-last-backup-recovery-point-created": { + "enabled": true, + "level": 2 + }, + "dynamodb-pitr-enabled": { + "enabled": true, + "level": 2 + }, + "dynamodb-table-deletion-protection-enabled": { + "enabled": true, + "level": 1 + }, + "dynamodb-table-encrypted-kms": { + "enabled": true, + "level": 2 + }, + "dynamodb-table-encryption-enabled": { + "enabled": true, + "level": 2 + } + } + }, + "ECR": { + "enabled": true, + "rules": { + "ecr-private-image-scanning-enabled": { + "enabled": true, + "level": 2 + }, + "ecr-private-lifecycle-policy-configured": { + "enabled": true, + "level": 2 + }, + "ecr-private-tag-immutability-enabled": { + "enabled": true, + "level": 2 + }, + "ecr-kms-encryption-1": { + "enabled": true, + "level": 2 + } + } + }, + "ECS": { + "enabled": true, + "rules": { + "ecs-awsvpc-networking-enabled": { + "enabled": true, + "level": 2 + }, + "ecs-containers-nonprivileged": { + "enabled": true, + "level": 2 + }, + "ecs-containers-readonly-access": { + "enabled": true, + "level": 2 + }, + "ecs-container-insights-enabled": { + "enabled": true, + "level": 2 + }, + "ecs-fargate-latest-platform-version": { + "enabled": true, + "level": 2 + }, + "ecs-task-definition-log-configuration": { + "enabled": true, + "level": 2 + }, + "ecs-task-definition-memory-hard-limit": { + "enabled": true, + "level": 1 + }, + "ecs-task-definition-nonroot-user": { + "enabled": true, + "level": 1 + } + } + }, + "EFS": { + "enabled": true, + "rules": { + "efs-access-point-enforce-root-directory": { + "enabled": true, + "level": 2 + }, + "efs-access-point-enforce-user-identity": { + "enabled": true, + "level": 2 + }, + "efs-automatic-backups-enabled": { + "enabled": true, + "level": 2 + }, + "efs-encrypted-check": { + "enabled": true, + "level": 2 + }, + "efs-mount-target-public-accessible": { + "enabled": true, + "level": 2 + } + } + }, + "EKS": { + "enabled": true, + "rules": { + "eks-cluster-logging-enabled": { + "enabled": true, + "level": 2 + }, + "eks-cluster-secrets-encrypted": { + "enabled": true, + "level": 2 + }, + "eks-endpoint-no-public-access": { + "enabled": true, + "level": 1 + }, + "eks-secrets-encrypted": { + "enabled": true, + "level": 2 + } + } + }, + "ElastiCache": { + "enabled": true, + "rules": { + "elasticache-auto-minor-version-upgrade-check": { + "enabled": true, + "level": 2 + }, + "elasticache-redis-cluster-automatic-backup-check": { + "enabled": true, + "level": 2 + }, + "elasticache-repl-grp-auto-failover-enabled": { + "enabled": true, + "level": 2 + }, + "elasticache-repl-grp-encrypted-at-rest": { + "enabled": true, + "level": 2 + }, + "elasticache-repl-grp-encrypted-in-transit": { + "enabled": true, + "level": 2 + }, + "elasticache-subnet-group-check": { + "enabled": true, + "level": 2 + } + } + }, + "IAM": { + "enabled": true, + "rules": { + "iam-policy-no-statements-with-admin-access": { + "enabled": true, + "level": 1 + }, + "iam-policy-no-statements-with-full-access": { + "enabled": true, + "level": 1 + }, + "iam-role-managed-policy-check": { + "enabled": true, + "level": 1 + } + } + }, + "Lambda": { + "enabled": true, + "rules": { + "lambda-dlq-check": { + "enabled": true, + "level": 1 + }, + "lambda-function-public-access-prohibited": { + "enabled": true, + "level": 2 + }, + "lambda-function-settings-check": { + "enabled": true, + "level": 2 + }, + "lambda-inside-vpc": { + "enabled": true, + "level": 1 + } + } + }, + "Tags": { + "enabled": true, + "rules": { + "required-tags": { + "enabled": true, + "level": 2 + } + } + }, + "Route53": { + "enabled": true, + "rules": { + "route53-query-logging-enabled": { + "enabled": true, + "level": 2 + } + } + }, + "S3": { + "enabled": true, + "rules": { + "s3-access-point-in-vpc-only": { + "enabled": true, + "level": 1 + }, + "s3-bucket-default-lock-enabled": { + "enabled": true, + "level": 1 + }, + "s3-bucket-level-public-access-prohibited": { + "enabled": true, + "level": 2 + }, + "s3-bucket-logging-enabled": { + "enabled": true, + "level": 1 + }, + "s3-bucket-ssl-requests-only": { + "enabled": true, + "level": 2 + }, + "s3-bucket-versioning-enabled": { + "enabled": true, + "level": 2 + }, + "s3-default-encryption-kms": { + "enabled": true, + "level": 2 + }, + "s3-event-notifications-enabled": { + "enabled": true, + "level": 1 + }, + "s3-last-backup-recovery-point-created": { + "enabled": true, + "level": 1 + }, + "s3-lifecycle-policy-check": { + "enabled": true, + "level": 2 + } + } + }, + "Secrets Manager": { + "enabled": true, + "rules": { + "secretsmanager-rotation-enabled-check": { + "enabled": true, + "level": 2 + }, + "secretsmanager-scheduled-rotation-success-check": { + "enabled": true, + "level": 1 + }, + "secretsmanager-secret-periodic-rotation": { + "enabled": true, + "level": 2 + } + } + }, + "Security Hub": { + "enabled": true, + "rules": { + "securityhub-enabled": { + "enabled": true, + "level": 1 + } + } + }, + "SNS": { + "enabled": true, + "rules": { + "sns-encrypted-kms": { + "enabled": true, + "level": 2 + }, + "sns-topic-message-delivery-notification-enabled": { + "enabled": true, + "level": 2 + } + } + }, + "VPC": { + "enabled": true, + "rules": { + "ec2-transit-gateway-auto-vpc-attach-disabled": { + "enabled": true, + "level": 1 + }, + "restricted-ssh": { + "enabled": true, + "level": 2 + }, + "restricted-common-ports": { + "enabled": true, + "level": 2 + }, + "subnet-auto-assign-public-ip-disabled": { + "enabled": true, + "level": 1 + }, + "vpc-default-security-group-closed": { + "enabled": true, + "level": 2 + }, + "vpc-flow-logs-enabled": { + "enabled": true, + "level": 2 + }, + "vpc-network-acl-unused-check": { + "enabled": true, + "level": 2 + }, + "vpc-peering-dns-resolution-check": { + "enabled": true, + "level": 2 + }, + "vpc-sg-open-only-to-authorized-ports": { + "enabled": true, + "level": 2 + } + } + }, + "WAFv2": { + "enabled": true, + "rules": { + "wafv2-logging-enabled": { + "enabled": true, + "level": 2 + }, + "wafv2-rulegroup-logging-enabled": { + "enabled": true, + "level": 2 + }, + "wafv2-rulegroup-not-empty": { + "enabled": true, + "level": 2 + }, + "wafv2-webacl-not-empty": { + "enabled": true, + "level": 2 + } + } + } +} \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..e1ec865 --- /dev/null +++ b/main.py @@ -0,0 +1,93 @@ +import json +from pprint import pprint + +from PyInquirer import prompt, style_from_dict, Token +from colorama import Style, Fore + +from utils import * +import services + +custom_style_2 = style_from_dict( + { + Token.Separator: "#6C6C6C", + Token.QuestionMark: "#FF9D00 bold", + # Token.Selected: '', # default + Token.Selected: "#5F819D", + Token.Pointer: "#FF9D00 bold", + Token.Instruction: "", # default + Token.Answer: "#5F819D bold", + Token.Question: "", + } +) + + +def ask_services_to_enable(bp): + cli_questions = [ + { + "type": "checkbox", + "message": "Select AWS Services to inspect", + "name": "services", + "choices": [ + {"name": k, "checked": bool(v["enabled"])} for k, v in bp.items() + ], + } + ] + + answers = prompt(questions=cli_questions, style=custom_style_2) + for service in bp.keys(): + bp[service]["enabled"] = service in answers["services"] + return bp + + +def perform_bp_rules_check(bp): + for service_name, service in bp.items(): + if not service["enabled"]: + continue + if service_name == "Lambda": + service_name = "_lambda" + + module = getattr(services, convert_snake_case(service_name)) + for rule_name, rule in service["rules"].items(): + if not rule["enabled"]: + continue + + rule["result"] = getattr(module, convert_snake_case(rule_name))() + return bp + + +def show_bp_result(bp): + for service_name, service in bp.items(): + if not service["enabled"]: + continue + print(f"{'=' * 25} {service_name + ' ':=<30}") + + for rule_name, rule in service["rules"].items(): + if not rule["enabled"]: + continue + + if rule["result"].passed: + style = Style.DIM + color = Fore.GREEN + mark = "✅" + elif rule["level"] == 2 and not rule["result"].passed: + style = Style.BRIGHT + color = Fore.RED + mark = "❌" + elif rule["level"] == 1 and not rule["result"].passed: + style = Style.NORMAL + color = Fore.LIGHTRED_EX + mark = "❕" + + print(f"{style}{rule_name:50}{Style.RESET_ALL} - {color}{mark}{Fore.RESET}") + for resource in rule["result"].non_compliant_resources: + print(f" - {color}{resource}{Fore.RESET}") + print() + + +if __name__ == "__main__": + bp = load_bp_from_file() + bp = ask_services_to_enable(bp) + save_bp_to_file(bp) + + bp = perform_bp_rules_check(bp) + show_bp_result(bp) diff --git a/models.py b/models.py new file mode 100644 index 0000000..64ff21d --- /dev/null +++ b/models.py @@ -0,0 +1,7 @@ +from pydantic import BaseModel + + +class RuleCheckResult(BaseModel): + passed: bool + compliant_resources: list[str] + non_compliant_resources: list[str] diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..485ab9c --- /dev/null +++ b/requirements.txt @@ -0,0 +1,17 @@ +annotated-types==0.7.0 +boto3==1.34.153 +botocore==1.34.153 +colorama==0.4.6 +jmespath==1.0.1 +prompt-toolkit==1.0.14 +pydantic==2.8.2 +pydantic_core==2.20.1 +Pygments==2.18.0 +PyInquirer==1.0.3 +python-dateutil==2.9.0.post0 +regex==2024.7.24 +s3transfer==0.10.2 +six==1.16.0 +typing_extensions==4.12.2 +urllib3==1.26.19 +wcwidth==0.2.13 diff --git a/services/__init__.py b/services/__init__.py new file mode 100644 index 0000000..8d2903d --- /dev/null +++ b/services/__init__.py @@ -0,0 +1,28 @@ +from . import ( + alb, + api_gw, + rds, + asg, + ec2, + cloudfront, + kms, + codeseries, + cloudwatch, + docdb, + dynamodb, + ecr, + ecs, + efs, + eks, + elasticache, + iam, + _lambda, + tags, + route53, + s3, + secrets_manager, + security_hub, + sns, + vpc, + wafv2, +) diff --git a/services/_lambda.py b/services/_lambda.py new file mode 100644 index 0000000..e588883 --- /dev/null +++ b/services/_lambda.py @@ -0,0 +1,29 @@ +from models import RuleCheckResult +import boto3 + + +# client = boto3.client("") + + +def lambda_dlq_check(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def lambda_function_public_access_prohibited(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def lambda_function_settings_check(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def lambda_inside_vpc(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) diff --git a/services/alb.py b/services/alb.py new file mode 100644 index 0000000..864b0de --- /dev/null +++ b/services/alb.py @@ -0,0 +1,37 @@ +from models import RuleCheckResult +import boto3 + + +# client = boto3.client("") + + +def alb_http_drop_invalid_header_enabled(): + return RuleCheckResult( + passed=False, + compliant_resources=[], + non_compliant_resources=[], + ) + + +def alb_waf_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def elb_cross_zone_load_balancing_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def elb_deletion_protection_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def elb_logging_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) diff --git a/services/api_gw.py b/services/api_gw.py new file mode 100644 index 0000000..f62f619 --- /dev/null +++ b/services/api_gw.py @@ -0,0 +1,41 @@ +from models import RuleCheckResult +import boto3 + + +# client = boto3.client("") + + +def api_gwv2_access_logs_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def api_gwv2_authorization_type_configured(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def api_gw_associated_with_waf(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def api_gw_cache_enabled_and_encrypted(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def api_gw_execution_logging_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def api_gw_xray_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) diff --git a/services/asg.py b/services/asg.py new file mode 100644 index 0000000..987cfbc --- /dev/null +++ b/services/asg.py @@ -0,0 +1,17 @@ +from models import RuleCheckResult +import boto3 + + +# client = boto3.client("") + + +def autoscaling_group_elb_healthcheck_required(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def autoscaling_multiple_az(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) diff --git a/services/cloudfront.py b/services/cloudfront.py new file mode 100644 index 0000000..98c189f --- /dev/null +++ b/services/cloudfront.py @@ -0,0 +1,41 @@ +from models import RuleCheckResult +import boto3 + + +# client = boto3.client("") + + +def cloudfront_accesslogs_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def cloudfront_associated_with_waf(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def cloudfront_default_root_object_configured(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def cloudfront_no_deprecated_ssl_protocols(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def cloudfront_s3_origin_access_control_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def cloudfront_viewer_policy_https(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) diff --git a/services/cloudwatch.py b/services/cloudwatch.py new file mode 100644 index 0000000..cd61ce6 --- /dev/null +++ b/services/cloudwatch.py @@ -0,0 +1,17 @@ +from models import RuleCheckResult +import boto3 + + +# client = boto3.client("") + + +def cw_loggroup_retention_period_check(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def cloudwatch_alarm_settings_check(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) diff --git a/services/codeseries.py b/services/codeseries.py new file mode 100644 index 0000000..ca98a50 --- /dev/null +++ b/services/codeseries.py @@ -0,0 +1,23 @@ +from models import RuleCheckResult +import boto3 + + +# client = boto3.client("") + + +def codebuild_project_environment_privileged_check(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def codebuild_project_logging_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def codedeploy_auto_rollback_monitor_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) diff --git a/services/docdb.py b/services/docdb.py new file mode 100644 index 0000000..c9c163b --- /dev/null +++ b/services/docdb.py @@ -0,0 +1,29 @@ +from models import RuleCheckResult +import boto3 + + +# client = boto3.client("") + + +def docdb_cluster_audit_logging_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def docdb_cluster_backup_retention_check(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def docdb_cluster_deletion_protection_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def docdb_cluster_encrypted(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) diff --git a/services/dynamodb.py b/services/dynamodb.py new file mode 100644 index 0000000..5c185bc --- /dev/null +++ b/services/dynamodb.py @@ -0,0 +1,41 @@ +from models import RuleCheckResult +import boto3 + + +# client = boto3.client("") + + +def dynamodb_autoscaling_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def dynamodb_last_backup_recovery_point_created(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def dynamodb_pitr_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def dynamodb_table_deletion_protection_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def dynamodb_table_encrypted_kms(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def dynamodb_table_encryption_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) diff --git a/services/ec2.py b/services/ec2.py new file mode 100644 index 0000000..b0c8422 --- /dev/null +++ b/services/ec2.py @@ -0,0 +1,59 @@ +from models import RuleCheckResult +import boto3 + + +# client = boto3.client("") + + +def autoscaling_launch_template(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def ec2_ebs_encryption_by_default(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def ec2_imdsv2_check(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def ec2_instance_detailed_monitoring_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def ec2_instance_managed_by_systems_manager(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def ec2_instance_profile_attached(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def ec2_no_amazon_key_pair(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def ec2_stopped_instance(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def ec2_token_hop_limit_check(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) diff --git a/services/ecr.py b/services/ecr.py new file mode 100644 index 0000000..93b0254 --- /dev/null +++ b/services/ecr.py @@ -0,0 +1,29 @@ +from models import RuleCheckResult +import boto3 + + +# client = boto3.client("") + + +def ecr_private_image_scanning_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def ecr_private_lifecycle_policy_configured(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def ecr_private_tag_immutability_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def ecr_kms_encryption_1(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) diff --git a/services/ecs.py b/services/ecs.py new file mode 100644 index 0000000..6e2f04a --- /dev/null +++ b/services/ecs.py @@ -0,0 +1,53 @@ +from models import RuleCheckResult +import boto3 + + +# client = boto3.client("") + + +def ecs_awsvpc_networking_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def ecs_containers_nonprivileged(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def ecs_containers_readonly_access(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def ecs_container_insights_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def ecs_fargate_latest_platform_version(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def ecs_task_definition_log_configuration(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def ecs_task_definition_memory_hard_limit(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def ecs_task_definition_nonroot_user(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) diff --git a/services/efs.py b/services/efs.py new file mode 100644 index 0000000..0672e44 --- /dev/null +++ b/services/efs.py @@ -0,0 +1,35 @@ +from models import RuleCheckResult +import boto3 + + +# client = boto3.client("") + + +def efs_access_point_enforce_root_directory(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def efs_access_point_enforce_user_identity(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def efs_automatic_backups_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def efs_encrypted_check(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def efs_mount_target_public_accessible(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) diff --git a/services/eks.py b/services/eks.py new file mode 100644 index 0000000..87017e9 --- /dev/null +++ b/services/eks.py @@ -0,0 +1,29 @@ +from models import RuleCheckResult +import boto3 + + +# client = boto3.client("") + + +def eks_cluster_logging_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def eks_cluster_secrets_encrypted(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def eks_endpoint_no_public_access(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def eks_secrets_encrypted(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) diff --git a/services/elasticache.py b/services/elasticache.py new file mode 100644 index 0000000..4fdfa2e --- /dev/null +++ b/services/elasticache.py @@ -0,0 +1,41 @@ +from models import RuleCheckResult +import boto3 + + +# client = boto3.client("") + + +def elasticache_auto_minor_version_upgrade_check(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def elasticache_redis_cluster_automatic_backup_check(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def elasticache_repl_grp_auto_failover_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def elasticache_repl_grp_encrypted_at_rest(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def elasticache_repl_grp_encrypted_in_transit(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def elasticache_subnet_group_check(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) diff --git a/services/iam.py b/services/iam.py new file mode 100644 index 0000000..896cce9 --- /dev/null +++ b/services/iam.py @@ -0,0 +1,23 @@ +from models import RuleCheckResult +import boto3 + + +# client = boto3.client("") + + +def iam_policy_no_statements_with_admin_access(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def iam_policy_no_statements_with_full_access(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def iam_role_managed_policy_check(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) diff --git a/services/kms.py b/services/kms.py new file mode 100644 index 0000000..8eda301 --- /dev/null +++ b/services/kms.py @@ -0,0 +1,11 @@ +from models import RuleCheckResult +import boto3 + + +# client = boto3.client("") + + +def cmk_backing_key_rotation_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) diff --git a/services/rds.py b/services/rds.py new file mode 100644 index 0000000..544ee8d --- /dev/null +++ b/services/rds.py @@ -0,0 +1,95 @@ +from models import RuleCheckResult +import boto3 + + +# client = boto3.client("") + + +def aurora_last_backup_recovery_point_created(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def aurora_mysql_backtracking_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def db_instance_backup_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def rds_cluster_auto_minor_version_upgrade_enable(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def rds_cluster_default_admin_check(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def rds_cluster_deletion_protection_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def rds_cluster_encrypted_at_rest(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def rds_cluster_iam_authentication_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def rds_cluster_multi_az_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def rds_db_security_group_not_allowed(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def rds_enhanced_monitoring_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def rds_instance_deletion_protection_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def rds_instance_public_access_check(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def rds_logging_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def rds_snapshot_encrypted(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) diff --git a/services/route53.py b/services/route53.py new file mode 100644 index 0000000..3db31b7 --- /dev/null +++ b/services/route53.py @@ -0,0 +1,11 @@ +from models import RuleCheckResult +import boto3 + + +# client = boto3.client("") + + +def route53_query_logging_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) diff --git a/services/s3.py b/services/s3.py new file mode 100644 index 0000000..da8fe1a --- /dev/null +++ b/services/s3.py @@ -0,0 +1,65 @@ +from models import RuleCheckResult +import boto3 + + +# client = boto3.client("") + + +def s3_access_point_in_vpc_only(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def s3_bucket_default_lock_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def s3_bucket_level_public_access_prohibited(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def s3_bucket_logging_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def s3_bucket_ssl_requests_only(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def s3_bucket_versioning_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def s3_default_encryption_kms(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def s3_event_notifications_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def s3_last_backup_recovery_point_created(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def s3_lifecycle_policy_check(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) diff --git a/services/secrets_manager.py b/services/secrets_manager.py new file mode 100644 index 0000000..4aac790 --- /dev/null +++ b/services/secrets_manager.py @@ -0,0 +1,23 @@ +from models import RuleCheckResult +import boto3 + + +# client = boto3.client("") + + +def secretsmanager_rotation_enabled_check(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def secretsmanager_scheduled_rotation_success_check(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def secretsmanager_secret_periodic_rotation(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) diff --git a/services/security_hub.py b/services/security_hub.py new file mode 100644 index 0000000..d20866c --- /dev/null +++ b/services/security_hub.py @@ -0,0 +1,11 @@ +from models import RuleCheckResult +import boto3 + + +# client = boto3.client("") + + +def securityhub_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) diff --git a/services/sns.py b/services/sns.py new file mode 100644 index 0000000..b8addee --- /dev/null +++ b/services/sns.py @@ -0,0 +1,17 @@ +from models import RuleCheckResult +import boto3 + + +# client = boto3.client("") + + +def sns_encrypted_kms(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def sns_topic_message_delivery_notification_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) diff --git a/services/tags.py b/services/tags.py new file mode 100644 index 0000000..e27b50e --- /dev/null +++ b/services/tags.py @@ -0,0 +1,11 @@ +from models import RuleCheckResult +import boto3 + + +# client = boto3.client("") + + +def required_tags(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) diff --git a/services/vpc.py b/services/vpc.py new file mode 100644 index 0000000..ed9dae3 --- /dev/null +++ b/services/vpc.py @@ -0,0 +1,257 @@ +from models import RuleCheckResult +from pprint import pprint +import boto3 + + +ec2 = boto3.client("ec2") + + +def ec2_transit_gateway_auto_vpc_attach_disabled(): + response = ec2.describe_transit_gateways() + + non_compliant_resources = [ + resource["TransitGatewayArn"] + for resource in filter( + lambda x: x["Options"]["AutoAcceptSharedAttachments"] == "enable", + response["TransitGateways"], + ) + ] + + compliant_resources = list( + set([resource["TransitGatewayArn"] for resource in response["TransitGateways"]]) + - set(non_compliant_resources) + ) + + return RuleCheckResult( + passed=not non_compliant_resources, + compliant_resources=compliant_resources, + non_compliant_resources=non_compliant_resources, + ) + + +def restricted_ssh(): + response = ec2.describe_security_group_rules() + + non_compliant_resources = [ + f'{resource["GroupId"]} / {resource["SecurityGroupRuleId"]}' + for resource in filter( + lambda x: x["IsEgress"] == False + and x["FromPort"] <= 22 + and x["ToPort"] >= 22 + and x.get("CidrIpv4") == "0.0.0.0/0", + response["SecurityGroupRules"], + ) + ] + + compliant_resources = list( + set( + [ + f'{resource["GroupId"]} / {resource["SecurityGroupRuleId"]}' + for resource in response["SecurityGroupRules"] + ] + ) + - set(non_compliant_resources) + ) + return RuleCheckResult( + passed=not non_compliant_resources, + compliant_resources=compliant_resources, + non_compliant_resources=non_compliant_resources, + ) + + +def restricted_common_ports(): + common_ports = [ + 22, # SSH + 80, # HTTP + 3306, # MySQL + 3389, # RDP + 5432, # PostgreSQL + 6379, # Redis + 11211, # Memcached + ] + response = ec2.describe_security_group_rules() + + non_compliant_resources = [ + f'{resource["GroupId"]} / {resource["SecurityGroupRuleId"]}' + for resource in filter( + lambda x: x["IsEgress"] == False + and x["FromPort"] in common_ports + and x["ToPort"] in common_ports + and x.get("PrefixListId") is None, + response["SecurityGroupRules"], + ) + ] + + compliant_resources = list( + set( + f'{resource["GroupId"]} / {resource["SecurityGroupRuleId"]}' + for resource in response["SecurityGroupRules"] + ) + - set(non_compliant_resources) + ) + + return RuleCheckResult( + passed=not non_compliant_resources, + compliant_resources=compliant_resources, + non_compliant_resources=non_compliant_resources, + ) + + +def subnet_auto_assign_public_ip_disabled(): + response = ec2.describe_subnets() + + non_compliant_resources = [ + resource["SubnetId"] + for resource in filter(lambda x: x["MapPublicIpOnLaunch"], response["Subnets"]) + ] + + compliant_resources = list( + set(resource["SubnetId"] for resource in response["Subnets"]) + - set(non_compliant_resources) + ) + + return RuleCheckResult( + passed=not non_compliant_resources, + compliant_resources=compliant_resources, + non_compliant_resources=non_compliant_resources, + ) + + +def vpc_default_security_group_closed(): + response = ec2.describe_security_groups( + Filters=[{"Name": "group-name", "Values": ["default"]}] + ) + + non_compliant_resources = [ + resource["GroupId"] + for resource in filter( + lambda x: x["IpPermissions"] or x["IpPermissionsEgress"], + response["SecurityGroups"], + ) + ] + + compliant_resources = list( + set(resource["GroupId"] for resource in response["SecurityGroups"]) + - set(non_compliant_resources) + ) + + return RuleCheckResult( + passed=not non_compliant_resources, + compliant_resources=compliant_resources, + non_compliant_resources=non_compliant_resources, + ) + + +def vpc_flow_logs_enabled(): + response = ec2.describe_flow_logs() + flow_log_enabled_vpcs = [ + resource["ResourceId"] for resource in response["FlowLogs"] + ] + + response = ec2.describe_vpcs() + + non_compliant_resources = [ + resource["VpcId"] + for resource in filter( + lambda x: x["VpcId"] not in flow_log_enabled_vpcs, response["Vpcs"] + ) + ] + + compliant_resources = list( + set(resource["VpcId"] for resource in response["Vpcs"]) + - set(non_compliant_resources) + ) + + return RuleCheckResult( + passed=not non_compliant_resources, + compliant_resources=compliant_resources, + non_compliant_resources=non_compliant_resources, + ) + + +def vpc_network_acl_unused_check(): + response = ec2.describe_network_acls() + + non_compliant_resources = [ + resource["NetworkAclId"] + for resource in filter(lambda x: not x["Associations"], response["NetworkAcls"]) + ] + + compliant_resources = list( + set(resource["NetworkAclId"] for resource in response["NetworkAcls"]) + - set(non_compliant_resources) + ) + + return RuleCheckResult( + passed=not non_compliant_resources, + compliant_resources=compliant_resources, + non_compliant_resources=non_compliant_resources, + ) + + +def vpc_peering_dns_resolution_check(): + response = ec2.describe_vpc_peering_connections() + + non_compliant_resources = [ + resource["VpcPeeringConnectionId"] + for resource in filter( + lambda x: x["Status"]["Code"] not in ["deleted", "deleting"] + and ( + not x["AccepterVpcInfo"].get("PeeringOptions") + or not x["AccepterVpcInfo"]["PeeringOptions"][ + "AllowDnsResolutionFromRemoteVpc" + ] + or not x["RequesterVpcInfo"]["PeeringOptions"][ + "AllowDnsResolutionFromRemoteVpc" + ] + ), + response["VpcPeeringConnections"], + ) + ] + + compliant_resources = list( + set( + resource["VpcPeeringConnectionId"] + for resource in response["VpcPeeringConnections"] + ) + - set(non_compliant_resources) + ) + + return RuleCheckResult( + passed=not non_compliant_resources, + compliant_resources=compliant_resources, + non_compliant_resources=non_compliant_resources, + ) + + +def vpc_sg_open_only_to_authorized_ports(): + response = ec2.describe_security_group_rules() + + authorized_port = [ + # 80 + ] + + non_compliant_resources = [ + f'{resource["GroupId"]} / {resource["SecurityGroupRuleId"]}' + for resource in filter( + lambda x: x["IsEgress"] == False + and (x.get("CidrIpv4") == "0.0.0.0/0" or x.get("CidrIpv6") == "::/0") + and x["FromPort"] not in authorized_port + and x["ToPort"] not in authorized_port, + response["SecurityGroupRules"], + ) + ] + + compliant_resources = list( + set( + f'{resource["GroupId"]} / {resource["SecurityGroupRuleId"]}' + for resource in response["SecurityGroupRules"] + ) + - set(non_compliant_resources) + ) + + return RuleCheckResult( + passed=not non_compliant_resources, + compliant_resources=compliant_resources, + non_compliant_resources=non_compliant_resources, + ) diff --git a/services/wafv2.py b/services/wafv2.py new file mode 100644 index 0000000..2831d00 --- /dev/null +++ b/services/wafv2.py @@ -0,0 +1,29 @@ +from models import RuleCheckResult +import boto3 + + +# client = boto3.client("") + + +def wafv2_logging_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def wafv2_rulegroup_logging_enabled(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def wafv2_rulegroup_not_empty(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) + + +def wafv2_webacl_not_empty(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..90eca73 --- /dev/null +++ b/utils.py @@ -0,0 +1,68 @@ +import json +import shutil + + +def load_bp_from_file(filepath="bp.json"): + try: + with open(filepath, "r") as f: + content = "".join(f.readlines()) + except FileNotFoundError: + shutil.copy("bp-base.json", filepath) + with open(filepath, "r") as f: + content = "".join(f.readlines()) + + return json.loads(content) + + +def save_bp_to_file(bp, filepath="bp.json"): + with open(filepath, "w") as f: + f.write(json.dumps(bp, indent=2)) + + +def convert_snake_case(text): + return text.lower().replace(" ", "_").replace("-", "_") + + +def convert_bp_to_snake_case(bp): + bp = { + service_name.lower().replace(" ", "_"): value + for service_name, value in bp.items() + } + for v in bp.values(): + v["rules"] = { + rule_name.lower().replace("-", "_"): rule + for rule_name, rule in v["rules"].items() + } + return bp + + +if __name__ == "__main__": + bp = load_bp_from_file() + rules = [ + ( + k.lower().replace(" ", "_"), + list(map(lambda x: x.replace("-", "_"), v["rules"].keys())), + ) + for k, v in bp.items() + ] + print(json.dumps(rules, indent=2)) + for rule in rules: + file_name = rule[0] + rule_names = rule[1] + file_template = f"""from models import RuleCheckResult +import boto3 + + +# client = boto3.client("") +""" + with open(f"services/{file_name}.py", "w") as f: + f.write(file_template) + for rule_name in rule_names: + function_template = f""" + +def {rule_name}(): + return RuleCheckResult( + passed=False, compliant_resources=[], non_compliant_resources=[] + ) +""" + f.write(function_template)