From 49b2a559ae8a174b7a717b84a9bef815c1e81dd8 Mon Sep 17 00:00:00 2001 From: Sebastian Nyberg <23510320+senyberg@users.noreply.github.com> Date: Mon, 12 Jun 2023 10:44:10 +0300 Subject: [PATCH] feat(vpc): add check `vpc_subnet_no_public_ip_by_default` (#2472) Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com> --- .../__init__.py | 0 ...bnet_no_public_ip_by_default.metadata.json | 32 +++++ .../vpc_subnet_no_public_ip_by_default.py | 27 ++++ ...vpc_subnet_no_public_ip_by_default_test.py | 122 ++++++++++++++++++ 4 files changed, 181 insertions(+) create mode 100644 prowler/providers/aws/services/vpc/vpc_subnet_no_public_ip_by_default/__init__.py create mode 100644 prowler/providers/aws/services/vpc/vpc_subnet_no_public_ip_by_default/vpc_subnet_no_public_ip_by_default.metadata.json create mode 100644 prowler/providers/aws/services/vpc/vpc_subnet_no_public_ip_by_default/vpc_subnet_no_public_ip_by_default.py create mode 100644 tests/providers/aws/services/vpc/vpc_subnet_no_public_ip_by_default/vpc_subnet_no_public_ip_by_default_test.py diff --git a/prowler/providers/aws/services/vpc/vpc_subnet_no_public_ip_by_default/__init__.py b/prowler/providers/aws/services/vpc/vpc_subnet_no_public_ip_by_default/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/prowler/providers/aws/services/vpc/vpc_subnet_no_public_ip_by_default/vpc_subnet_no_public_ip_by_default.metadata.json b/prowler/providers/aws/services/vpc/vpc_subnet_no_public_ip_by_default/vpc_subnet_no_public_ip_by_default.metadata.json new file mode 100644 index 00000000..48f90334 --- /dev/null +++ b/prowler/providers/aws/services/vpc/vpc_subnet_no_public_ip_by_default/vpc_subnet_no_public_ip_by_default.metadata.json @@ -0,0 +1,32 @@ +{ + "Provider": "aws", + "CheckID": "vpc_subnet_no_public_ip_by_default", + "CheckTitle": "Ensure VPC subnets do not assign public IP by default", + "CheckType": [ + "Infrastructure Security" + ], + "ServiceName": "vpc", + "SubServiceName": "subnet", + "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "Severity": "medium", + "ResourceType": "AwsEc2Subnet", + "Description": "Ensure VPC subnets do not assign public IP by default", + "Risk": "VPC subnet is a part of the VPC having its own rules for traffic. Assigning the Public IP to the subnet automatically (on launch) can accidentally expose the instances within this subnet to internet and should be edited to 'No' post creation of the Subnet.", + "RelatedUrl": "https://docs.aws.amazon.com/config/latest/developerguide/subnet-auto-assign-public-ip-disabled.html", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "https://docs.bridgecrew.io/docs/ensure-vpc-subnets-do-not-assign-public-ip-by-default#terraform" + }, + "Recommendation": { + "Text": "VPC subnets should not allow automatic public IP assignment", + "Url": "https://docs.aws.amazon.com/config/latest/developerguide/subnet-auto-assign-public-ip-disabled.html" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/vpc/vpc_subnet_no_public_ip_by_default/vpc_subnet_no_public_ip_by_default.py b/prowler/providers/aws/services/vpc/vpc_subnet_no_public_ip_by_default/vpc_subnet_no_public_ip_by_default.py new file mode 100644 index 00000000..97cd659d --- /dev/null +++ b/prowler/providers/aws/services/vpc/vpc_subnet_no_public_ip_by_default/vpc_subnet_no_public_ip_by_default.py @@ -0,0 +1,27 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.vpc.vpc_client import vpc_client + + +class vpc_subnet_no_public_ip_by_default(Check): + def execute(self): + findings = [] + for vpc in vpc_client.vpcs.values(): + for subnet in vpc.subnets: + report = Check_Report_AWS(self.metadata()) + report.region = subnet.region + report.resource_tags = subnet.tags + report.resource_id = subnet.id + + if subnet.mapPublicIpOnLaunch: + report.status = "FAIL" + report.status_extended = ( + f"VPC subnet {subnet.id} assigns public IP by default" + ) + else: + report.status = "PASS" + report.status_extended = ( + f"VPC subnet {subnet.id} does NOT assign public IP by default" + ) + findings.append(report) + + return findings diff --git a/tests/providers/aws/services/vpc/vpc_subnet_no_public_ip_by_default/vpc_subnet_no_public_ip_by_default_test.py b/tests/providers/aws/services/vpc/vpc_subnet_no_public_ip_by_default/vpc_subnet_no_public_ip_by_default_test.py new file mode 100644 index 00000000..3b966494 --- /dev/null +++ b/tests/providers/aws/services/vpc/vpc_subnet_no_public_ip_by_default/vpc_subnet_no_public_ip_by_default_test.py @@ -0,0 +1,122 @@ +from unittest import mock + +from boto3 import client, session +from moto import mock_ec2 + +from prowler.providers.aws.lib.audit_info.models import AWS_Audit_Info + +AWS_REGION = "us-east-1" +AWS_ACCOUNT_NUMBER = "123456789012" + + +class Test_vpc_subnet_separate_private_public: + def set_mocked_audit_info(self): + audit_info = AWS_Audit_Info( + session_config=None, + original_session=None, + audit_session=session.Session( + profile_name=None, + botocore_session=None, + ), + audited_account=AWS_ACCOUNT_NUMBER, + audited_user_id=None, + audited_partition="aws", + audited_identity_arn=None, + profile=None, + profile_region=None, + credentials=None, + assumed_role_info=None, + audited_regions=["us-east-1", "eu-west-1"], + organizations_metadata=None, + audit_resources=None, + ) + + return audit_info + + @mock_ec2 + def test_vpc_with_map_ip_on_launch(self): + ec2_client = client("ec2", region_name=AWS_REGION) + vpc = ec2_client.create_vpc( + CidrBlock="172.28.7.0/24", InstanceTenancy="default" + ) + subnet_private = ec2_client.create_subnet( + VpcId=vpc["Vpc"]["VpcId"], + CidrBlock="172.28.7.192/26", + AvailabilityZone=f"{AWS_REGION}a", + ) + + ec2_client.modify_subnet_attribute( + SubnetId=subnet_private["Subnet"]["SubnetId"], + MapPublicIpOnLaunch={"Value": True}, + ) + + from prowler.providers.aws.services.vpc.vpc_service import VPC + + current_audit_info = self.set_mocked_audit_info() + + with mock.patch( + "prowler.providers.aws.lib.audit_info.audit_info.current_audit_info", + new=current_audit_info, + ): + with mock.patch( + "prowler.providers.aws.services.vpc.vpc_subnet_separate_private_public.vpc_subnet_separate_private_public.vpc_client", + new=VPC(current_audit_info), + ): + from prowler.providers.aws.services.vpc.vpc_subnet_no_public_ip_by_default.vpc_subnet_no_public_ip_by_default import ( + vpc_subnet_no_public_ip_by_default, + ) + + check = vpc_subnet_no_public_ip_by_default() + results = check.execute() + + for result in results: + if result.resource_id == subnet_private["Subnet"]["SubnetId"]: + assert result.status == "FAIL" + assert ( + result.status_extended + == f"VPC subnet {subnet_private['Subnet']['SubnetId']} assigns public IP by default" + ) + + @mock_ec2 + def test_vpc_without_map_ip_on_launch(self): + ec2_client = client("ec2", region_name=AWS_REGION) + vpc = ec2_client.create_vpc( + CidrBlock="172.28.7.0/24", InstanceTenancy="default" + ) + subnet_private = ec2_client.create_subnet( + VpcId=vpc["Vpc"]["VpcId"], + CidrBlock="172.28.7.192/26", + AvailabilityZone=f"{AWS_REGION}a", + ) + + ec2_client.modify_subnet_attribute( + SubnetId=subnet_private["Subnet"]["SubnetId"], + MapPublicIpOnLaunch={"Value": False}, + ) + + from prowler.providers.aws.services.vpc.vpc_service import VPC + + current_audit_info = self.set_mocked_audit_info() + + with mock.patch( + "prowler.providers.aws.lib.audit_info.audit_info.current_audit_info", + new=current_audit_info, + ): + with mock.patch( + "prowler.providers.aws.services.vpc.vpc_subnet_separate_private_public.vpc_subnet_separate_private_public.vpc_client", + new=VPC(current_audit_info), + ): + from prowler.providers.aws.services.vpc.vpc_subnet_no_public_ip_by_default.vpc_subnet_no_public_ip_by_default import ( + vpc_subnet_no_public_ip_by_default, + ) + + check = vpc_subnet_no_public_ip_by_default() + results = check.execute() + + for result in results: + if result.resource_id == subnet_private["Subnet"]["SubnetId"]: + assert result.status == "PASS" + assert ( + result.status_extended + == f"VPC subnet {subnet_private['Subnet']['SubnetId']} does NOT assign public IP by default" + )