From 3370475fe95fa314dc9143c5b17694a7d9a8595a Mon Sep 17 00:00:00 2001 From: Sergio Garcia <38561120+sergargar@users.noreply.github.com> Date: Thu, 17 Nov 2022 20:30:27 +0100 Subject: [PATCH] feat(ELB): add ELB and ELBv2 tests and checks (#1489) Co-authored-by: sergargar --- providers/aws/aws_provider.py | 4 + providers/aws/services/elb/__init__.py | 0 providers/aws/services/elb/check_extra7129 | 81 ------ providers/aws/services/elb/check_extra7142 | 48 ---- providers/aws/services/elb/check_extra7150 | 48 ---- providers/aws/services/elb/check_extra7155 | 54 ---- providers/aws/services/elb/check_extra7158 | 47 ---- providers/aws/services/elb/check_extra7159 | 46 ---- providers/aws/services/elb/check_extra717 | 65 ----- providers/aws/services/elb/check_extra79 | 53 ---- providers/aws/services/elb/check_extra792 | 143 ----------- providers/aws/services/elb/check_extra793 | 124 --------- providers/aws/services/elb/elb_client.py | 4 + .../elb/elb_insecure_ssl_ciphers/__init__.py | 0 .../elb_insecure_ssl_ciphers.metadata.json | 35 +++ .../elb_insecure_ssl_ciphers.py | 28 ++ .../elb_insecure_ssl_ciphers_test.py | 129 ++++++++++ .../elb/elb_internet_facing/__init__.py | 0 .../elb_internet_facing.metadata.json | 35 +++ .../elb_internet_facing.py | 22 ++ .../elb_internet_facing_test.py | 122 +++++++++ .../elb/elb_logging_enabled/__init__.py | 0 .../elb_logging_enabled.metadata.json | 35 +++ .../elb_logging_enabled.py | 22 ++ .../elb_logging_enabled_test.py | 134 ++++++++++ providers/aws/services/elb/elb_service.py | 91 +++++++ .../aws/services/elb/elb_service_test.py | 126 +++++++++ .../elb/elb_ssl_listeners/__init__.py | 0 .../elb_ssl_listeners.metadata.json | 35 +++ .../elb_ssl_listeners/elb_ssl_listeners.py | 24 ++ .../elb_ssl_listeners_test.py | 120 +++++++++ providers/aws/services/elbv2/__init__.py | 0 providers/aws/services/elbv2/elbv2_client.py | 4 + .../elbv2_deletion_protection/__init__.py | 0 .../elbv2_deletion_protection.metadata.json | 35 +++ .../elbv2_deletion_protection.py | 23 ++ .../elbv2_deletion_protection_test.py | 145 +++++++++++ .../elbv2_desync_mitigation_mode/__init__.py | 0 ...elbv2_desync_mitigation_mode.metadata.json | 35 +++ .../elbv2_desync_mitigation_mode.py | 22 ++ .../elbv2_desync_mitigation_mode_test.py | 145 +++++++++++ .../elbv2_insecure_ssl_ciphers/__init__.py | 0 .../elbv2_insecure_ssl_ciphers.metadata.json | 35 +++ .../elbv2_insecure_ssl_ciphers.py | 39 +++ .../elbv2_insecure_ssl_ciphers_test.py | 178 +++++++++++++ .../elbv2/elbv2_internet_facing/__init__.py | 0 .../elbv2_internet_facing.metadata.json | 35 +++ .../elbv2_internet_facing.py | 23 ++ .../elbv2_internet_facing_test.py | 131 ++++++++++ .../elbv2_listeners_underneath/__init__.py | 0 .../elbv2_listeners_underneath.metadata.json | 35 +++ .../elbv2_listeners_underneath.py | 21 ++ .../elbv2_listeners_underneath_test.py | 150 +++++++++++ .../elbv2/elbv2_logging_enabled/__init__.py | 0 .../elbv2_logging_enabled.metadata.json | 35 +++ .../elbv2_logging_enabled.py | 25 ++ .../elbv2_logging_enabled_test.py | 151 +++++++++++ .../elbv2/elbv2_request_smugling/__init__.py | 0 .../elbv2_request_smugling.metadata.json | 35 +++ .../elbv2_request_smugling.py | 26 ++ .../elbv2_request_smugling_test.py | 151 +++++++++++ providers/aws/services/elbv2/elbv2_service.py | 162 ++++++++++++ .../aws/services/elbv2/elbv2_service_test.py | 228 +++++++++++++++++ .../elbv2/elbv2_ssl_listeners/__init__.py | 0 .../elbv2_ssl_listeners.metadata.json | 35 +++ .../elbv2_ssl_listeners.py | 36 +++ .../elbv2_ssl_listeners_test.py | 239 ++++++++++++++++++ .../elbv2/elbv2_waf_acl_attached/__init__.py | 0 .../elbv2_waf_acl_attached.metadata.json | 35 +++ .../elbv2_waf_acl_attached.py | 31 +++ .../elbv2_waf_acl_attached_test.py | 213 ++++++++++++++++ .../iam_disable_45_days_credentials_test.py | 1 - ...m_no_expired_server_certificates_stored.py | 1 - providers/aws/services/waf/__init__.py | 0 providers/aws/services/waf/waf_client.py | 4 + providers/aws/services/waf/waf_service.py | 68 +++++ .../aws/services/waf/waf_service_test.py | 108 ++++++++ providers/aws/services/wafv2/__init__.py | 0 providers/aws/services/wafv2/wafv2_client.py | 4 + providers/aws/services/wafv2/wafv2_service.py | 70 +++++ .../aws/services/wafv2/wafv2_service_test.py | 125 +++++++++ 81 files changed, 3803 insertions(+), 711 deletions(-) create mode 100644 providers/aws/services/elb/__init__.py delete mode 100644 providers/aws/services/elb/check_extra7129 delete mode 100644 providers/aws/services/elb/check_extra7142 delete mode 100644 providers/aws/services/elb/check_extra7150 delete mode 100644 providers/aws/services/elb/check_extra7155 delete mode 100644 providers/aws/services/elb/check_extra7158 delete mode 100644 providers/aws/services/elb/check_extra7159 delete mode 100644 providers/aws/services/elb/check_extra717 delete mode 100644 providers/aws/services/elb/check_extra79 delete mode 100644 providers/aws/services/elb/check_extra792 delete mode 100644 providers/aws/services/elb/check_extra793 create mode 100644 providers/aws/services/elb/elb_client.py create mode 100644 providers/aws/services/elb/elb_insecure_ssl_ciphers/__init__.py create mode 100644 providers/aws/services/elb/elb_insecure_ssl_ciphers/elb_insecure_ssl_ciphers.metadata.json create mode 100644 providers/aws/services/elb/elb_insecure_ssl_ciphers/elb_insecure_ssl_ciphers.py create mode 100644 providers/aws/services/elb/elb_insecure_ssl_ciphers/elb_insecure_ssl_ciphers_test.py create mode 100644 providers/aws/services/elb/elb_internet_facing/__init__.py create mode 100644 providers/aws/services/elb/elb_internet_facing/elb_internet_facing.metadata.json create mode 100644 providers/aws/services/elb/elb_internet_facing/elb_internet_facing.py create mode 100644 providers/aws/services/elb/elb_internet_facing/elb_internet_facing_test.py create mode 100644 providers/aws/services/elb/elb_logging_enabled/__init__.py create mode 100644 providers/aws/services/elb/elb_logging_enabled/elb_logging_enabled.metadata.json create mode 100644 providers/aws/services/elb/elb_logging_enabled/elb_logging_enabled.py create mode 100644 providers/aws/services/elb/elb_logging_enabled/elb_logging_enabled_test.py create mode 100644 providers/aws/services/elb/elb_service.py create mode 100644 providers/aws/services/elb/elb_service_test.py create mode 100644 providers/aws/services/elb/elb_ssl_listeners/__init__.py create mode 100644 providers/aws/services/elb/elb_ssl_listeners/elb_ssl_listeners.metadata.json create mode 100644 providers/aws/services/elb/elb_ssl_listeners/elb_ssl_listeners.py create mode 100644 providers/aws/services/elb/elb_ssl_listeners/elb_ssl_listeners_test.py create mode 100644 providers/aws/services/elbv2/__init__.py create mode 100644 providers/aws/services/elbv2/elbv2_client.py create mode 100644 providers/aws/services/elbv2/elbv2_deletion_protection/__init__.py create mode 100644 providers/aws/services/elbv2/elbv2_deletion_protection/elbv2_deletion_protection.metadata.json create mode 100644 providers/aws/services/elbv2/elbv2_deletion_protection/elbv2_deletion_protection.py create mode 100644 providers/aws/services/elbv2/elbv2_deletion_protection/elbv2_deletion_protection_test.py create mode 100644 providers/aws/services/elbv2/elbv2_desync_mitigation_mode/__init__.py create mode 100644 providers/aws/services/elbv2/elbv2_desync_mitigation_mode/elbv2_desync_mitigation_mode.metadata.json create mode 100644 providers/aws/services/elbv2/elbv2_desync_mitigation_mode/elbv2_desync_mitigation_mode.py create mode 100644 providers/aws/services/elbv2/elbv2_desync_mitigation_mode/elbv2_desync_mitigation_mode_test.py create mode 100644 providers/aws/services/elbv2/elbv2_insecure_ssl_ciphers/__init__.py create mode 100644 providers/aws/services/elbv2/elbv2_insecure_ssl_ciphers/elbv2_insecure_ssl_ciphers.metadata.json create mode 100644 providers/aws/services/elbv2/elbv2_insecure_ssl_ciphers/elbv2_insecure_ssl_ciphers.py create mode 100644 providers/aws/services/elbv2/elbv2_insecure_ssl_ciphers/elbv2_insecure_ssl_ciphers_test.py create mode 100644 providers/aws/services/elbv2/elbv2_internet_facing/__init__.py create mode 100644 providers/aws/services/elbv2/elbv2_internet_facing/elbv2_internet_facing.metadata.json create mode 100644 providers/aws/services/elbv2/elbv2_internet_facing/elbv2_internet_facing.py create mode 100644 providers/aws/services/elbv2/elbv2_internet_facing/elbv2_internet_facing_test.py create mode 100644 providers/aws/services/elbv2/elbv2_listeners_underneath/__init__.py create mode 100644 providers/aws/services/elbv2/elbv2_listeners_underneath/elbv2_listeners_underneath.metadata.json create mode 100644 providers/aws/services/elbv2/elbv2_listeners_underneath/elbv2_listeners_underneath.py create mode 100644 providers/aws/services/elbv2/elbv2_listeners_underneath/elbv2_listeners_underneath_test.py create mode 100644 providers/aws/services/elbv2/elbv2_logging_enabled/__init__.py create mode 100644 providers/aws/services/elbv2/elbv2_logging_enabled/elbv2_logging_enabled.metadata.json create mode 100644 providers/aws/services/elbv2/elbv2_logging_enabled/elbv2_logging_enabled.py create mode 100644 providers/aws/services/elbv2/elbv2_logging_enabled/elbv2_logging_enabled_test.py create mode 100644 providers/aws/services/elbv2/elbv2_request_smugling/__init__.py create mode 100644 providers/aws/services/elbv2/elbv2_request_smugling/elbv2_request_smugling.metadata.json create mode 100644 providers/aws/services/elbv2/elbv2_request_smugling/elbv2_request_smugling.py create mode 100644 providers/aws/services/elbv2/elbv2_request_smugling/elbv2_request_smugling_test.py create mode 100644 providers/aws/services/elbv2/elbv2_service.py create mode 100644 providers/aws/services/elbv2/elbv2_service_test.py create mode 100644 providers/aws/services/elbv2/elbv2_ssl_listeners/__init__.py create mode 100644 providers/aws/services/elbv2/elbv2_ssl_listeners/elbv2_ssl_listeners.metadata.json create mode 100644 providers/aws/services/elbv2/elbv2_ssl_listeners/elbv2_ssl_listeners.py create mode 100644 providers/aws/services/elbv2/elbv2_ssl_listeners/elbv2_ssl_listeners_test.py create mode 100644 providers/aws/services/elbv2/elbv2_waf_acl_attached/__init__.py create mode 100644 providers/aws/services/elbv2/elbv2_waf_acl_attached/elbv2_waf_acl_attached.metadata.json create mode 100644 providers/aws/services/elbv2/elbv2_waf_acl_attached/elbv2_waf_acl_attached.py create mode 100644 providers/aws/services/elbv2/elbv2_waf_acl_attached/elbv2_waf_acl_attached_test.py create mode 100644 providers/aws/services/waf/__init__.py create mode 100644 providers/aws/services/waf/waf_client.py create mode 100644 providers/aws/services/waf/waf_service.py create mode 100644 providers/aws/services/waf/waf_service_test.py create mode 100644 providers/aws/services/wafv2/__init__.py create mode 100644 providers/aws/services/wafv2/wafv2_client.py create mode 100644 providers/aws/services/wafv2/wafv2_service.py create mode 100644 providers/aws/services/wafv2/wafv2_service_test.py diff --git a/providers/aws/aws_provider.py b/providers/aws/aws_provider.py index b93a314e..c69cfba9 100644 --- a/providers/aws/aws_provider.py +++ b/providers/aws/aws_provider.py @@ -309,6 +309,10 @@ def generate_regional_clients(service: str, audit_info: AWS_Audit_Info) -> dict: json_regions = data["services"]["dynamodb"]["regions"][ audit_info.audited_partition ] + elif service == "elbv2": + json_regions = data["services"]["elb"]["regions"][audit_info.audited_partition] + elif service == "wafv2" or service == "waf-regional": + json_regions = data["services"]["waf"]["regions"][audit_info.audited_partition] else: json_regions = data["services"][service]["regions"][ audit_info.audited_partition diff --git a/providers/aws/services/elb/__init__.py b/providers/aws/services/elb/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/providers/aws/services/elb/check_extra7129 b/providers/aws/services/elb/check_extra7129 deleted file mode 100644 index 74a309c1..00000000 --- a/providers/aws/services/elb/check_extra7129 +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env bash - -# Prowler - the handy cloud security tool (copyright 2018) by Toni de la Fuente -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy -# of the License at http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed -# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -# CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. -CHECK_ID_extra7129="7.129" -CHECK_TITLE_extra7129="[extra7129] Check if Application Load Balancer has a WAF ACL attached" -CHECK_SCORED_extra7129="NOT_SCORED" -CHECK_CIS_LEVEL_extra7129="EXTRA" -CHECK_SEVERITY_extra7129="Medium" -CHECK_ASFF_RESOURCE_TYPE_extra7129="AwsElasticLoadBalancingV2LoadBalancer" -CHECK_ALTERNATE_check7129="extra7129" -CHECK_ASFF_COMPLIANCE_TYPE_extra7129="ens-mp.s.2.aws.waf.3" -CHECK_SERVICENAME_extra7129="elb" -CHECK_RISK_extra7129='If not WAF ACL is attached risk of web attacks increases.' -CHECK_REMEDIATION_extra7129='Using the AWS Management Console open the AWS WAF console to attach an ACL.' -CHECK_DOC_extra7129='https://docs.aws.amazon.com/waf/latest/developerguide/web-acl-associating-aws-resource.html' -CHECK_CAF_EPIC_extra7129='Infrastructure Security' - -PARALLEL_REGIONS="50" - -extra7129(){ - for regx in $REGIONS; do - # ( - LIST_OF_ELBSV2=$($AWSCLI elbv2 describe-load-balancers $PROFILE_OPT --region $regx --query 'LoadBalancers[?Scheme == `internet-facing` && Type == `application`].[LoadBalancerName]' --output text 2>&1) - if [[ $(echo "$LIST_OF_ELBSV2" | grep -E 'AccessDenied|UnauthorizedOperation|AuthorizationError') ]]; then - textInfo "$regx: Access Denied trying to describe load balancers" "$regx" - continue - fi - LIST_OF_WAFV2_WEBACL_ARN=$($AWSCLI wafv2 list-web-acls $PROFILE_OPT --region=$regx --scope=REGIONAL --query WebACLs[*].ARN --output text) - LIST_OF_WAFV1_WEBACL_WEBACLID=$($AWSCLI waf-regional list-web-acls $PROFILE_OPT --region $regx --query WebACLs[*].[WebACLId] --output text) - - if [[ $LIST_OF_ELBSV2 ]]; then - for alb in $LIST_OF_ELBSV2; do - if [[ ${#LIST_OF_WAFV2_WEBACL_ARN[@]} -gt 0 || ${#LIST_OF_WAFV1_WEBACL_WEBACLID[@]} -gt 0 ]]; then - WAF_PROTECTED_ALBS=() - for wafaclarn in $LIST_OF_WAFV2_WEBACL_ARN; do - ALB_RESOURCES_IN_WEBACL=$($AWSCLI wafv2 list-resources-for-web-acl $PROFILE_OPT --web-acl-arn $wafaclarn --region=$regx --resource-type APPLICATION_LOAD_BALANCER --query ResourceArns --output text | xargs -n1 | awk -F'/' '{ print $3 }'| grep $alb) - if [[ $ALB_RESOURCES_IN_WEBACL ]]; then - WAF_PROTECTED_ALBS+=($wafaclarn) - fi - done - for wafv1aclid in $LIST_OF_WAFV1_WEBACL_WEBACLID; do - ALB_RESOURCES_IN_WEBACL=$($AWSCLI waf-regional list-resources-for-web-acl $PROFILE_OPT --web-acl-id $wafv1aclid --region=$regx --resource-type APPLICATION_LOAD_BALANCER --output text --query "[ResourceArns]"| grep $alb) - if [[ $ALB_RESOURCES_IN_WEBACL ]]; then - WAFv1_PROTECTED_ALBS+=($wafv1aclid) - fi - done - if [[ ${#WAF_PROTECTED_ALBS[@]} -gt 0 || ${#WAFv1_PROTECTED_ALBS[@]} -gt 0 ]]; then - if [[ ${#WAF_PROTECTED_ALBS[@]} -gt 0 ]]; then - for wafaclarn in "${WAF_PROTECTED_ALBS[@]}"; do - WAFV2_WEBACL_ARN_SHORT=$(echo $wafaclarn | awk -F'/' '{ print $3 }') - textPass "$regx: Application Load Balancer $alb is protected by WAFv2 ACL $WAFV2_WEBACL_ARN_SHORT" "$regx" "$alb" - done - fi - if [[ ${#WAFv1_PROTECTED_ALBS[@]} -gt 0 ]]; then - for wafv1aclid in "${WAFv1_PROTECTED_ALBS[@]}"; do - textPass "$regx: Application Load Balancer $alb is protected by WAFv1 ACL $wafv1aclid" "$regx" "$alb" - done - fi - else - textFail "$regx: Application Load Balancer $alb is not protected by WAF ACL" "$regx" "$alb" - fi - else - textFail "$regx: Application Load Balancer $alb is not protected no WAF ACL found" "$regx" "$alb" - fi - done - else - textInfo "$regx: No Application Load Balancers found" "$regx" - fi - # ) & - done - # wait -} diff --git a/providers/aws/services/elb/check_extra7142 b/providers/aws/services/elb/check_extra7142 deleted file mode 100644 index f11a1486..00000000 --- a/providers/aws/services/elb/check_extra7142 +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env bash - -# Prowler - the handy cloud security tool (copyright 2018) by Toni de la Fuente -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy -# of the License at http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed -# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -# CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. -CHECK_ID_extra7142="7.142" -CHECK_TITLE_extra7142="[extra7142] Check if Application Load Balancer is dropping invalid packets to prevent header based HTTP request smuggling" -CHECK_SCORED_extra7142="NOT_SCORED" -CHECK_CIS_LEVEL_extra7142="EXTRA" -CHECK_SEVERITY_extra7142="Medium" -CHECK_ASFF_RESOURCE_TYPE_extra7142="AwsElasticLoadBalancingV2LoadBalancer" -CHECK_ALTERNATE_check7142="extra7142" -CHECK_ASFF_COMPLIANCE_TYPE_extra7142="" -CHECK_SERVICENAME_extra7142="elb" -CHECK_RISK_extra7142='ALB can be target of actors sending bad HTTP headers' -CHECK_REMEDIATION_extra7142='Ensure Application Load Balancer is configured for HTTP headers with header fields that are not valid are removed by the load balancer (true)' -CHECK_DOC_extra7142='https://docs.aws.amazon.com/elasticloadbalancing/latest/application/application-load-balancers.html#desync-mitigation-mode' -CHECK_CAF_EPIC_extra7142='Data Protection' - - -extra7142(){ - for regx in $REGIONS; do - LIST_OF_ELBSV2=$($AWSCLI elbv2 describe-load-balancers $PROFILE_OPT --region $regx --query 'LoadBalancers[?Type == `application`].[LoadBalancerArn]' --output text 2>&1) - if [[ $(echo "$LIST_OF_ELBSV2" | grep -E 'AccessDenied|UnauthorizedOperation|AuthorizationError') ]]; then - textInfo "$regx: Access Denied trying to describe load balancers" "$regx" - continue - fi - if [[ $LIST_OF_ELBSV2 ]];then - for alb in $LIST_OF_ELBSV2;do - CHECK_IF_DROP_INVALID_HEADER_FIELDS=$($AWSCLI elbv2 describe-load-balancer-attributes $PROFILE_OPT --region $regx --load-balancer-arn $alb --query 'Attributes[6]' --output text|grep -i true) - if [[ $CHECK_IF_DROP_INVALID_HEADER_FIELDS ]];then - textPass "$regx: Application Load Balancer $alb is dropping invalid header fields." "$regx" "$alb" - else - textFail "$regx: Application Load Balancer $alb is not dropping invalid header fields" "$regx" "$alb" - fi - done - else - textInfo "$regx: no ALBs found" - fi - done -} diff --git a/providers/aws/services/elb/check_extra7150 b/providers/aws/services/elb/check_extra7150 deleted file mode 100644 index c9f945a6..00000000 --- a/providers/aws/services/elb/check_extra7150 +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env bash - -# Prowler - the handy cloud security tool (copyright 2018) by Toni de la Fuente -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy -# of the License at http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed -# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -# CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. -CHECK_ID_extra7150="7.150" -CHECK_TITLE_extra7150="[extra7150] Check if Elastic Load Balancers have deletion protection enabled" -CHECK_SCORED_extra7150="NOT_SCORED" -CHECK_CIS_LEVEL_extra7150="EXTRA" -CHECK_SEVERITY_extra7150="Medium" -CHECK_ASFF_RESOURCE_TYPE_extra7150="AwsElbLoadBalancer" -CHECK_ALTERNATE_check7150="extra7150" -CHECK_SERVICENAME_extra7150="elb" -CHECK_RISK_extra7150='If deletion protection is not enabled; the resource is not protected against deletion.' -CHECK_REMEDIATION_extra7150='Enable deletion protection attribute; this is not enabled by default.' -CHECK_DOC_extra7150='https://docs.aws.amazon.com/elasticloadbalancing/latest/application/application-load-balancers.html#deletion-protection' -CHECK_CAF_EPIC_extra7150='Data Protection' - -extra7150(){ - # "Check if Elastic Load Balancers have delete protection enabled." - for regx in $REGIONS; do - LIST_OF_ELBSV2=$($AWSCLI elbv2 describe-load-balancers $PROFILE_OPT --region $regx --query 'LoadBalancers[*].LoadBalancerArn' --output text 2>&1|xargs -n1 ) - if [[ $(echo "$LIST_OF_ELBSV2" | grep -E 'AccessDenied|UnauthorizedOperation|AuthorizationError') ]]; then - textInfo "$regx: Access Denied trying to describe load balancers" "$regx" - continue - fi - if [[ $LIST_OF_ELBSV2 ]]; then - for elb in $LIST_OF_ELBSV2; do - CHECK_DELETION_PROTECTION_ENABLED=$($AWSCLI elbv2 describe-load-balancer-attributes $PROFILE_OPT --region $regx --load-balancer-arn $elb --query Attributes[*] --output text|grep "deletion_protection.enabled"|grep true ) - ELBV2_NAME=$(echo $elb|cut -d\/ -f3) - if [[ $CHECK_DELETION_PROTECTION_ENABLED ]]; then - textPass "$regx: $ELBV2_NAME has the attribute deletion protection enabled" "$regx" "$elb" - else - textFail "$regx: $ELBV2_NAME does not have deletion protection enabled." "$regx" "$elb" - fi - done - else - textInfo "$regx: No ELBs found" "$regx" - fi - done -} diff --git a/providers/aws/services/elb/check_extra7155 b/providers/aws/services/elb/check_extra7155 deleted file mode 100644 index 04554f61..00000000 --- a/providers/aws/services/elb/check_extra7155 +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env bash - -# Prowler - the handy cloud security tool (copyright 2019) by Toni de la Fuente -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy -# of the License at http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed -# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -# CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. -# Remediation: -# -# https://docs.aws.amazon.com/cli/latest/reference/elbv2/modify-load-balancer-attributes.html -# -# aws elbv2 modify-load-balancer-attributes -# --load-balancer-arn \ -# --attributes Key=routing.http.desync_mitigation_mode,Value= - -CHECK_ID_extra7155="7.155" -CHECK_TITLE_extra7155="[extra7155] Check whether the Application Load Balancer is configured with defensive or strictest desync mitigation mode" -CHECK_SCORED_extra7155="NOT_SCORED" -CHECK_CIS_LEVEL_extra7155="EXTRA" -CHECK_SEVERITY_extra7155="Medium" -CHECK_ASFF_RESOURCE_TYPE_extra7155="AwsElasticLoadBalancingV2LoadBalancer" -CHECK_ALTERNATE_check7155="extra7155" -CHECK_SERVICENAME_extra7155="elb" -CHECK_RISK_extra7155='HTTP Desync issues can lead to request smuggling and make your applications vulnerable to request queue or cache poisoning; which could lead to credential hijacking or execution of unauthorized commands.' -CHECK_REMEDIATION_extra7155='Ensure Application Load Balancer is configured with defensive or strictest desync mitigation mode' -CHECK_DOC_extra7155='https://aws.amazon.com/about-aws/whats-new/2020/08/application-and-classic-load-balancers-adding-defense-in-depth-with-introduction-of-desync-mitigation-mode/' -CHECK_CAF_EPIC_extra7155='Data Protection' - -extra7155() { - for regx in $REGIONS; do - LIST_OF_ELBSV2=$($AWSCLI elbv2 describe-load-balancers $PROFILE_OPT --region $regx --query 'LoadBalancers[?Type == `application`].[LoadBalancerArn]' --output text 2>&1) - if [[ $(echo "$LIST_OF_ELBSV2" | grep -E 'AccessDenied|UnauthorizedOperation|AuthorizationError') ]]; then - textInfo "$regx: Access Denied trying to describe load balancers" "$regx" - continue - fi - if [[ $LIST_OF_ELBSV2 ]];then - for alb in $LIST_OF_ELBSV2;do - CHECK_DESYNC_MITIGATION_MODE=$($AWSCLI elbv2 describe-load-balancer-attributes $PROFILE_OPT --region $regx --load-balancer-arn $alb --query 'Attributes[8]' --output json | jq -r '.Value') - if [[ $CHECK_DESYNC_MITIGATION_MODE == "monitor" ]]; then - textFail "$regx: Application load balancer $alb does not have desync mitigation mode set as defensive or strictest." "$regx" "$alb" - else - textPass "$regx: Application load balancer $alb is configured with correct desync mitigation mode." "$regx" "$alb" - fi - done - else - textInfo "$regx: No Application Load Balancers found" "$regx" - fi - done -} diff --git a/providers/aws/services/elb/check_extra7158 b/providers/aws/services/elb/check_extra7158 deleted file mode 100644 index dbed2d73..00000000 --- a/providers/aws/services/elb/check_extra7158 +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env bash - -# Prowler - the handy cloud security tool (copyright 2018) by Toni de la Fuente -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy -# of the License at http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed -# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -# CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. -CHECK_ID_extra7158="7.158" -CHECK_TITLE_extra7158="[extra7158] Check if ELBV2 has listeners underneath" -CHECK_SCORED_extra7158="NOT_SCORED" -CHECK_CIS_LEVEL_extra7158="EXTRA" -CHECK_SEVERITY_extra7158="Medium" -CHECK_ASFF_RESOURCE_TYPE_extra7158="AwsElbv2LoadBalancer" -CHECK_ALTERNATE_check7158="extra7158" -CHECK_SERVICENAME_extra7158="elb" -CHECK_RISK_extra7158='The rules that are defined for a listener determine how the load balancer routes requests to its registered targets.' -CHECK_REMEDIATION_extra7158='Add listeners to Elastic Load Balancers V2.' -CHECK_DOC_extra7158='https://docs.aws.amazon.com/elasticloadbalancing/latest/classic/elb-listener-config.html' -CHECK_CAF_EPIC_extra7158='Data Protection' - -extra7158(){ - for regx in $REGIONS; do - LIST_OF_ELBSV2=$($AWSCLI elbv2 describe-load-balancers --query 'LoadBalancers[*].LoadBalancerArn' $PROFILE_OPT --region $regx --output text 2>&1) - if [[ $(echo "$LIST_OF_ELBSV2" | grep -E 'AccessDenied|UnauthorizedOperation|AuthorizationError') ]]; then - textInfo "$regx: Access Denied trying to describe load balancers" "$regx" - continue - fi - if [[ $LIST_OF_ELBSV2 ]]; then - for elb in $LIST_OF_ELBSV2; do - LIST_OF_LISTENERS=$($AWSCLI elbv2 describe-listeners $PROFILE_OPT --region $regx --load-balancer-arn $elb --query 'Listeners[*]' --output text) - ELBV2_NAME=$(echo $elb|cut -d\/ -f3) - if [[ $LIST_OF_LISTENERS ]]; then - textPass "$regx: $ELBV2_NAME has listeners underneath" "$regx" "$elb" - else - textFail "$regx: $ELBV2_NAME has no listeners underneath" "$regx" "$elb" - fi - done - else - textInfo "$regx: No ELBs found" "$regx" - fi - done -} diff --git a/providers/aws/services/elb/check_extra7159 b/providers/aws/services/elb/check_extra7159 deleted file mode 100644 index 08accb6f..00000000 --- a/providers/aws/services/elb/check_extra7159 +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env bash - -# Prowler - the handy cloud security tool (copyright 2018) by Toni de la Fuente -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy -# of the License at http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed -# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -# CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. -CHECK_ID_extra7159="7.159" -CHECK_TITLE_extra7159="[extra7159] Check if ELB has listeners underneath" -CHECK_SCORED_extra7159="NOT_SCORED" -CHECK_CIS_LEVEL_extra7159="EXTRA" -CHECK_SEVERITY_extra7159="Medium" -CHECK_ASFF_RESOURCE_TYPE_extra7159="AwsElbLoadBalancer" -CHECK_ALTERNATE_check7159="extra7159" -CHECK_SERVICENAME_extra7159="elb" -CHECK_RISK_extra7159='The rules that are defined for a listener determine how the load balancer routes requests to its registered targets.' -CHECK_REMEDIATION_extra7159='Add listeners to Elastic Load Balancers.' -CHECK_DOC_extra7159='https://docs.aws.amazon.com/elasticloadbalancing/latest/classic/elb-listener-config.html' -CHECK_CAF_EPIC_extra7159='Data Protection' - -extra7159(){ - for regx in $REGIONS; do - LIST_OF_ELBS=$($AWSCLI elb describe-load-balancers --query 'LoadBalancerDescriptions[*].LoadBalancerName' $PROFILE_OPT --region $regx --output text 2>&1) - if [[ $(echo "$LIST_OF_ELBS" | grep -E 'AccessDenied|UnauthorizedOperation|AuthorizationError') ]]; then - textInfo "$regx: Access Denied trying to describe load balancers" "$regx" - continue - fi - if [[ $LIST_OF_ELBS ]]; then - for elb in $LIST_OF_ELBS; do - LIST_OF_LISTENERS=$($AWSCLI elb describe-load-balancers --load-balancer-name $elb --query 'LoadBalancerDescriptions[*].ListenerDescriptions' $PROFILE_OPT --region $regx --output text) - if [[ $LIST_OF_LISTENERS ]]; then - textPass "$regx: $elb has listeners underneath" "$regx" "$elb" - else - textFail "$regx: $elb has no listeners underneath" "$regx" "$elb" - fi - done - else - textInfo "$regx: No ELBs found" "$regx" - fi - done -} diff --git a/providers/aws/services/elb/check_extra717 b/providers/aws/services/elb/check_extra717 deleted file mode 100644 index b0006078..00000000 --- a/providers/aws/services/elb/check_extra717 +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env bash - -# Prowler - the handy cloud security tool (copyright 2018) by Toni de la Fuente -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy -# of the License at http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed -# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -# CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. -CHECK_ID_extra717="7.17" -CHECK_TITLE_extra717="[extra717] Check if Elastic Load Balancers have logging enabled" -CHECK_SCORED_extra717="NOT_SCORED" -CHECK_CIS_LEVEL_extra717="EXTRA" -CHECK_SEVERITY_extra717="Medium" -CHECK_ASFF_RESOURCE_TYPE_extra717="AwsElbLoadBalancer" -CHECK_ALTERNATE_check717="extra717" -CHECK_SERVICENAME_extra717="elb" -CHECK_RISK_extra717='If logs are not enabled monitoring of service use and threat analysis is not possible.' -CHECK_REMEDIATION_extra717='Enable ELB logging; create la log lifecycle and define use cases.' -CHECK_DOC_extra717='https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-access-logs.html' -CHECK_CAF_EPIC_extra717='Logging and Monitoring' - -extra717(){ - # "Check if Elastic Load Balancers have logging enabled " - for regx in $REGIONS; do - LIST_OF_ELBS=$($AWSCLI elb describe-load-balancers $PROFILE_OPT --region $regx --query 'LoadBalancerDescriptions[*].LoadBalancerName' --output text 2>&1 |xargs -n1) - if [[ $(echo "$LIST_OF_ELBS" | grep -E 'AccessDenied|UnauthorizedOperation') ]]; then - textInfo "$regx: Access Denied trying to list load balancers v1" "$regx" - continue - fi - LIST_OF_ELBSV2=$($AWSCLI elbv2 describe-load-balancers $PROFILE_OPT --region $regx --query 'LoadBalancers[*].LoadBalancerArn' --output text 2>&1 |xargs -n1) - if [[ $(echo "$LIST_OF_ELBSV2" | grep -E 'AccessDenied|UnauthorizedOperation') ]]; then - textInfo "$regx: Access Denied trying to list load balancers v2" "$regx" - continue - fi - if [[ $LIST_OF_ELBS || $LIST_OF_ELBSV2 ]]; then - if [[ $LIST_OF_ELBS ]]; then - for elb in $LIST_OF_ELBS; do - CHECK_ELBS_LOG_ENABLED=$($AWSCLI elb describe-load-balancer-attributes $PROFILE_OPT --region $regx --load-balancer-name $elb --query 'LoadBalancerAttributes.AccessLog.Enabled' |grep "^true") - if [[ $CHECK_ELBS_LOG_ENABLED ]]; then - textPass "$regx: $elb has access logs to S3 configured" "$regx" "$elb" - else - textFail "$regx: $elb has not configured access logs" "$regx" "$elb" - fi - done - fi - if [[ $LIST_OF_ELBSV2 ]]; then - for elbarn in $LIST_OF_ELBSV2; do - CHECK_ELBSV2_LOG_ENABLED=$($AWSCLI elbv2 describe-load-balancer-attributes $PROFILE_OPT --region $regx --load-balancer-arn $elbarn --query Attributes[*] --output text |grep "^access_logs.s3.enabled"|cut -f2|grep true) - ELBV2_NAME=$(echo $elbarn|cut -d\/ -f3) - if [[ $CHECK_ELBSV2_LOG_ENABLED ]]; then - textPass "$regx: $ELBV2_NAME has access logs to S3 configured" "$regx" "$elb" - else - textFail "$regx: $ELBV2_NAME has not configured access logs" "$regx" "$elb" - fi - done - fi - else - textInfo "$regx: No ELBs found" "$regx" - fi - done -} diff --git a/providers/aws/services/elb/check_extra79 b/providers/aws/services/elb/check_extra79 deleted file mode 100644 index 78b81aa0..00000000 --- a/providers/aws/services/elb/check_extra79 +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env bash - -# Prowler - the handy cloud security tool (copyright 2018) by Toni de la Fuente -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy -# of the License at http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed -# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -# CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. -CHECK_ID_extra79="7.9" -CHECK_TITLE_extra79="[extra79] Check for internet facing Elastic Load Balancers" -CHECK_SCORED_extra79="NOT_SCORED" -CHECK_CIS_LEVEL_extra79="EXTRA" -CHECK_SEVERITY_extra79="Medium" -CHECK_ASFF_RESOURCE_TYPE_extra79="AwsElbLoadBalancer" -CHECK_ALTERNATE_extra709="extra79" -CHECK_ALTERNATE_check79="extra79" -CHECK_ALTERNATE_check709="extra79" -CHECK_SERVICENAME_extra79="elb" -CHECK_RISK_extra79='Publicly accessible load balancers could expose sensitive data to bad actors.' -CHECK_REMEDIATION_extra79='Ensure the load balancer should be publicly accessible. If publicly exposed ensure a WAF ACL is implemented.' -CHECK_DOC_extra79='https://docs.aws.amazon.com/waf/latest/developerguide/web-acl-associating-aws-resource.html' -CHECK_CAF_EPIC_extra79='Data Protection' - -extra79(){ - # "Check for internet facing Elastic Load Balancers " - for regx in $REGIONS; do - LIST_OF_PUBLIC_ELBS=$($AWSCLI elb describe-load-balancers $PROFILE_OPT --region $regx --query 'LoadBalancerDescriptions[?Scheme == `internet-facing`].[LoadBalancerName,DNSName]' --output text 2>&1) - if [[ $(echo "$LIST_OF_PUBLIC_ELBS" | grep AccessDenied) ]]; then - textInfo "$regx: Access Denied Trying to describe load balancers" "$regx" - continue - fi - LIST_OF_PUBLIC_ELBSV2=$($AWSCLI elbv2 describe-load-balancers $PROFILE_OPT --region $regx --query 'LoadBalancers[?Scheme == `internet-facing`].[LoadBalancerName,DNSName]' --output text 2>&1) - if [[ $(echo "$LIST_OF_PUBLIC_ELBSV2" | grep AccessDenied) ]]; then - textInfo "$regx: Access Denied Trying to describe load balancers" "$regx" - continue - fi - LIST_OF_ALL_ELBS=$( echo $LIST_OF_PUBLIC_ELBS; echo $LIST_OF_PUBLIC_ELBSV2) - LIST_OF_ALL_ELBS_PER_LINE=$( echo $LIST_OF_ALL_ELBS| xargs -n2 ) - if [[ $LIST_OF_ALL_ELBS ]];then - while read -r elb;do - ELB_NAME=$(echo $elb | awk '{ print $1; }') - ELB_DNSNAME=$(echo $elb | awk '{ print $2; }') - textFail "$regx: ELB: $ELB_NAME at DNS: $ELB_DNSNAME is internet-facing!" "$regx" "$ELB_NAME" - done <<< "$LIST_OF_ALL_ELBS_PER_LINE" - else - textPass "$regx: no Internet Facing ELBs found" "$regx" "$ELB_NAME" - fi - done -} diff --git a/providers/aws/services/elb/check_extra792 b/providers/aws/services/elb/check_extra792 deleted file mode 100644 index 64e4fc5c..00000000 --- a/providers/aws/services/elb/check_extra792 +++ /dev/null @@ -1,143 +0,0 @@ -#!/usr/bin/env bash - -# Prowler - the handy cloud security tool (copyright 2018) by Toni de la Fuente -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy -# of the License at http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed -# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -# CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. -CHECK_ID_extra792="7.92" -CHECK_TITLE_extra792="[extra792] Check if Elastic Load Balancers have insecure SSL ciphers " -CHECK_SCORED_extra792="NOT_SCORED" -CHECK_CIS_LEVEL_extra792="EXTRA" -CHECK_SEVERITY_extra792="Medium" -CHECK_ASFF_RESOURCE_TYPE_extra792="AwsElbLoadBalancer" -CHECK_ALTERNATE_check792="extra792" -CHECK_ASFF_COMPLIANCE_TYPE_extra792="ens-mp.com.2.aws.elb.2" -CHECK_SERVICENAME_extra792="elb" -CHECK_RISK_extra792='Using insecure ciphers could affect privacy of in transit information.' -CHECK_REMEDIATION_extra792='Use a Security policy with a ciphers that are stronger as possible. Drop legacy and unsecure ciphers.' -CHECK_DOC_extra792='https://docs.aws.amazon.com/elasticloadbalancing/latest/classic/elb-ssl-security-policy.html' -CHECK_CAF_EPIC_extra792='Data Protection' - -extra792(){ - # "Check if Elastic Load Balancers have insecure SSL ciphers " - for regx in $REGIONS; do - LIST_OF_ELBS=$($AWSCLI elb describe-load-balancers $PROFILE_OPT --region $regx --query 'LoadBalancerDescriptions[*].LoadBalancerName' --output text 2>&1|xargs -n1 ) - if [[ $(echo "$LIST_OF_ELBS" | grep -E 'AccessDenied|UnauthorizedOperation|AuthorizationError') ]]; then - textInfo "$regx: Access Denied trying to describe load balancers" "$regx" - continue - fi - LIST_OF_ELBSV2=$($AWSCLI elbv2 describe-load-balancers $PROFILE_OPT --region $regx --query 'LoadBalancers[*].LoadBalancerArn' --output text 2>&1|xargs -n1 ) - if [[ $(echo "$LIST_OF_ELBS" | grep -E 'AccessDenied|UnauthorizedOperation|AuthorizationError') ]]; then - textInfo "$regx: Access Denied trying to describe load balancers" "$regx" - continue - fi - if [[ $LIST_OF_ELBS || $LIST_OF_ELBSV2 ]]; then - if [[ $LIST_OF_ELBS ]]; then - # https://docs.aws.amazon.com/elasticloadbalancing/latest/classic/elb-ssl-security-policy.html#ssl-ciphers - # https://docs.aws.amazon.com/elasticloadbalancing/latest/classic/elb-security-policy-table.html - ELBSECUREPOLICIES=("ELBSecurityPolicy-TLS-1-2-2017-01") - ELBSECURECIPHERS=("Protocol-TLSv1.2" "ECDHE-ECDSA-AES128-GCM-SHA256" "ECDHE-RSA-AES128-GCM-SHA256" "ECDHE-ECDSA-AES128-SHA256" "ECDHE-RSA-AES128-SHA256" "ECDHE-ECDSA-AES128-SHA" "ECDHE-RSA-AES128-SHA" "ECDHE-ECDSA-AES256-GCM-SHA384" "ECDHE-RSA-AES256-GCM-SHA384" "ECDHE-ECDSA-AES256-SHA384" "ECDHE-RSA-AES256-SHA384" "ECDHE-RSA-AES256-SHA" "ECDHE-ECDSA-AES256-SHA" "AES128-GCM-SHA256" "AES128-SHA256" "AES128-SHA" "AES256-GCM-SHA384" "AES256-SHA256" "AES256-SHA" "Server-Defined-Cipher-Order") - - for elb in $LIST_OF_ELBS; do - ELB_LISTENERS=$($AWSCLI elb describe-load-balancers $PROFILE_OPT --region $regx --load-balancer-name $elb --query "LoadBalancerDescriptions[0]" --output json) - - ELB_PROTOCOLS=$(echo $ELB_LISTENERS | jq -r '.ListenerDescriptions[].Listener.Protocol') - if [[ $(echo $ELB_PROTOCOLS | grep HTTPS) || $(echo $ELB_PROTOCOLS | grep SSL) ]]; then - ELB_POLICIES=$(echo $ELB_LISTENERS | jq -r '.ListenerDescriptions[].PolicyNames | .[]') - passed=true - for policy in $ELB_POLICIES; do - # Check for secure default policy - REFPOLICY=$($AWSCLI elb describe-load-balancer-policies $PROFILE_OPT --region $regx --load-balancer-name $elb --policy-name $policy --query "PolicyDescriptions[0].PolicyAttributeDescriptions[?(AttributeName == 'Reference-Security-Policy')].AttributeValue" --output text) - if [[ -n "$REFPOLICY" ]]; then - if array_contains ELBSECUREPOLICIES "$REFPOLICY"; then - continue # Passed for this listener/policy - else - passed=false - fi - else - # A custom policy is in use. Check Ciphers - CIPHERS=$($AWSCLI elb describe-load-balancer-policies $PROFILE_OPT --region $regx --load-balancer-name $elb --policy-name $policy --query "PolicyDescriptions[0].PolicyAttributeDescriptions[?(AttributeValue == 'true')].AttributeName" --output text) - for cipher in $CIPHERS; do - if array_contains ELBSECURECIPHERS "$cipher"; then - continue - else - passed=false - fi - done - fi - done - - if $passed; then - textPass "$regx: $elb has no insecure SSL ciphers" "$regx" "$elb" - else - textFail "$regx: $elb has insecure SSL ciphers" "$regx" "$elb" - fi - else - textInfo "$regx: $elb does not have an HTTPS or SSL listener" "$regx" - fi - done - fi - if [[ $LIST_OF_ELBSV2 ]]; then - # NOTE - ALBs do NOT support custom security policies - # https://docs.aws.amazon.com/elasticloadbalancing/latest/application/create-https-listener.html - ELBV2SECUREPOLICIES=("ELBSecurityPolicy-TLS-1-2-2017-01" "ELBSecurityPolicy-TLS-1-2-Ext-2018-06" "ELBSecurityPolicy-FS-1-2-2019-08" "ELBSecurityPolicy-FS-1-2-Res-2019-08" "ELBSecurityPolicy-FS-1-2-Res-2020-10" "ELBSecurityPolicy-TLS13-1-2-2021-06" "ELBSecurityPolicy-TLS13-1-3-2021-06" "ELBSecurityPolicy-TLS13-1-2-Res-2021-06" "ELBSecurityPolicy-TLS13-1-2-Ext1-2021-06" "ELBSecurityPolicy-TLS13-1-2-Ext2-2021-06") - - for elbarn in $LIST_OF_ELBSV2; do - passed=true - if [[ $(echo $elbarn | grep "loadbalancer/app/") ]]; then - elbname=$(echo $elbarn | awk -F 'loadbalancer/app/' '{print $2}' | awk -F '/' '{print $1}') - elif [[ $(echo $elbarn | grep "loadbalancer/net/") ]]; then - elbname=$(echo $elbarn | awk -F 'loadbalancer/net/' '{print $2}' | awk -F '/' '{print $1}') - else - elbname=$elbarn - fi - - ELBV2_LISTENERS=$($AWSCLI elbv2 describe-listeners $PROFILE_OPT --region $regx --load-balancer-arn $elbarn --query "Listeners[*]" --output json) - - ELBV2_PROTOCOLS=$(echo $ELBV2_LISTENERS | jq -r '.[].Protocol') - - if [[ $(echo $ELBV2_PROTOCOLS | grep HTTPS) || $(echo $ELBV2_PROTOCOLS | grep TLS) ]]; then - ELBV2_SSL_POLICIES=$($AWSCLI elbv2 describe-listeners $PROFILE_OPT --region $regx --load-balancer-arn $elbarn --query 'Listeners[*].SslPolicy' --output text) - - for policy in $ELBV2_SSL_POLICIES; do - if array_contains ELBV2SECUREPOLICIES "$policy"; then - continue # Passed for this listener/policy - else - passed=false - fi - done - - if $passed; then - textPass "$regx: $elbname has no insecure SSL ciphers" "$regx" "$elbname" - else - textFail "$regx: $elbname has insecure SSL ciphers" "$regx" "$elbname" - fi - else - textInfo "$regx: $elbname does not have an HTTPS or TLS listener" "$regx" - fi - done - fi - else - textInfo "$regx: No ELBs found" "$regx" - fi - done -} - -array_contains () { - local array="$1[@]" - local seeking=$2 - local in=1 - for element in "${!array}"; do - if [[ $element == "$seeking" ]]; then - in=0 - break - fi - done - return $in -} diff --git a/providers/aws/services/elb/check_extra793 b/providers/aws/services/elb/check_extra793 deleted file mode 100644 index fea5f8bc..00000000 --- a/providers/aws/services/elb/check_extra793 +++ /dev/null @@ -1,124 +0,0 @@ -#!/usr/bin/env bash - -# Prowler - the handy cloud security tool (copyright 2018) by Toni de la Fuente -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may not -# use this file except in compliance with the License. You may obtain a copy -# of the License at http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software distributed -# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -# CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. -CHECK_ID_extra793="7.93" -CHECK_TITLE_extra793="[extra793] Check if Elastic Load Balancers have SSL listeners " -CHECK_SCORED_extra793="NOT_SCORED" -CHECK_CIS_LEVEL_extra793="EXTRA" -CHECK_SEVERITY_extra793="Medium" -CHECK_ASFF_RESOURCE_TYPE_extra793="AwsElbLoadBalancer" -CHECK_ALTERNATE_check793="extra793" -CHECK_ASFF_COMPLIANCE_TYPE_extra793="ens-mp.com.2.aws.elb.1" -CHECK_SERVICENAME_extra793="elb" -CHECK_RISK_extra793='Clear text communication could affect privacy of information in transit.' -CHECK_REMEDIATION_extra793='Scan for Load Balancers with HTTP or TCP listeners and understand the reason for each of them. Check if the listener can be implemented as TLS instead.' -CHECK_DOC_extra793='https://docs.aws.amazon.com/elasticloadbalancing/latest/application/create-https-listener.html' -CHECK_CAF_EPIC_extra793='Data Protection' - -extra793(){ - # "Check if Elastic Load Balancers have encrypted listeners " - for regx in $REGIONS; do - LIST_OF_ELBS=$($AWSCLI elb describe-load-balancers $PROFILE_OPT --region $regx --query 'LoadBalancerDescriptions[*].LoadBalancerName' --output text 2>&1|xargs -n1) - if [[ $(echo "$LIST_OF_ELBS" | grep -E 'AccessDenied|UnauthorizedOperation|AuthorizationError') ]]; then - textInfo "$regx: Access Denied trying to describe load balancers" "$regx" - continue - fi - LIST_OF_ELBSV2=$($AWSCLI elbv2 describe-load-balancers $PROFILE_OPT --region $regx --query 'LoadBalancers[?(Type == `application`)].LoadBalancerArn' --output text 2>&1|xargs -n1) - if [[ $(echo "$LIST_OF_ELBSV2" | grep -E 'AccessDenied|UnauthorizedOperation|AuthorizationError') ]]; then - textInfo "$regx: Access Denied trying to describe load balancers" "$regx" - continue - fi - if [[ $LIST_OF_ELBS || $LIST_OF_ELBSV2 ]]; then - if [[ $LIST_OF_ELBS ]]; then - ENCRYPTEDPROTOCOLS=("HTTPS" "SSL") - for elb in $LIST_OF_ELBS; do - ELB_PROTOCOLS=$($AWSCLI elb describe-load-balancers $PROFILE_OPT --region $regx --load-balancer-name $elb --query "LoadBalancerDescriptions[0].ListenerDescriptions[*].Listener.Protocol" --output text) - passed=true - potential_redirect=false - for protocol in $ELB_PROTOCOLS; do - if array_contains ENCRYPTEDPROTOCOLS "$protocol"; then - continue - else - # Check if both HTTP and HTTPS in use - if [[ $(echo $ELB_PROTOCOLS | grep HTTPS) ]]; then - potential_redirect=true - fi - passed=false - fi - done - - if $passed; then - textPass "$regx: $elb has encrypted listeners" "$regx" - else - if $potential_redirect; then - textInfo "$regx: $elb has both encrypted and non-encrypted listeners" "$regx" - else - textFail "$regx: $elb has non-encrypted listeners" "$regx" "$elb" - fi - fi - done - fi - if [[ $LIST_OF_ELBSV2 ]]; then - for elbarn in $LIST_OF_ELBSV2; do - https_only=true - redirect_rule=false - elbname=$(echo $elbarn | awk -F 'loadbalancer/app/' '{print $2}' | awk -F '/' '{print $1}') - - ELBV2_LISTENERS=$($AWSCLI elbv2 describe-listeners $PROFILE_OPT --region $regx --load-balancer-arn $elbarn --query "Listeners[*]") - ELBV2_PROTOCOLS=$(echo $ELBV2_LISTENERS | jq -r '.[].Protocol') - - if [[ $(echo $ELBV2_PROTOCOLS | grep HTTPS) ]]; then - for line in $(echo $ELBV2_LISTENERS | jq -r '.[] | .Protocol + "," + .ListenerArn'); do - protocol=$(echo $line | awk -F ',' '{print $1}') - listenerArn=$(echo $line | awk -F ',' '{print $2}') - if [[ $protocol == "HTTP" ]]; then - https_only=false - # Check for redirect rule - ELBV2_RULES=$($AWSCLI elbv2 describe-rules $PROFILE_OPT --region $regx --listener-arn $listenerArn --query 'Rules[]') - if [[ $(echo $ELBV2_RULES | jq -r '.[].Actions[].RedirectConfig.Protocol' | grep HTTPS) ]]; then - redirect_rule=true - fi - fi - done - - if $https_only; then - textPass "$regx: $elbname has HTTPS listeners only" "$regx" - else - if $redirect_rule; then - textInfo "$regx: $elbname has HTTP listener that redirects to HTTPS" "$regx" - else - textFail "$regx: $elbname has non-encrypted listeners" "$regx" "$elbname" - fi - fi - else - textFail "$regx: $elbname has non-encrypted listeners" "$regx" "$elbname" - fi - done - fi - else - textInfo "$regx: No ELBs found" "$regx" - fi - done -} - -array_contains () { - local array="$1[@]" - local seeking=$2 - local in=1 - for element in "${!array}"; do - if [[ $element == "$seeking" ]]; then - in=0 - break - fi - done - return $in -} diff --git a/providers/aws/services/elb/elb_client.py b/providers/aws/services/elb/elb_client.py new file mode 100644 index 00000000..1e5e4864 --- /dev/null +++ b/providers/aws/services/elb/elb_client.py @@ -0,0 +1,4 @@ +from providers.aws.lib.audit_info.audit_info import current_audit_info +from providers.aws.services.elb.elb_service import ELB + +elb_client = ELB(current_audit_info) diff --git a/providers/aws/services/elb/elb_insecure_ssl_ciphers/__init__.py b/providers/aws/services/elb/elb_insecure_ssl_ciphers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/providers/aws/services/elb/elb_insecure_ssl_ciphers/elb_insecure_ssl_ciphers.metadata.json b/providers/aws/services/elb/elb_insecure_ssl_ciphers/elb_insecure_ssl_ciphers.metadata.json new file mode 100644 index 00000000..1cc0f3b1 --- /dev/null +++ b/providers/aws/services/elb/elb_insecure_ssl_ciphers/elb_insecure_ssl_ciphers.metadata.json @@ -0,0 +1,35 @@ +{ + "Provider": "aws", + "CheckID": "elb_insecure_ssl_ciphers", + "CheckTitle": "Check if Elastic Load Balancers have insecure SSL ciphers.", + "CheckType": ["Data Protection"], + "ServiceName": "elb", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "Severity": "medium", + "ResourceType": "AwsElbLoadBalancer", + "Description": "Check if Elastic Load Balancers have insecure SSL ciphers.", + "Risk": "Using insecure ciphers could affect privacy of in transit information.", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "aws elb set-load-balancer-policies-of-listener --load-balancer-name --load-balancer-port 443 --policy-names ELBSecurityPolicy-TLS-1-2-2017-01", + "NativeIaC": "", + "Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/ELB/elb-security-policy.html", + "Terraform": "https://docs.bridgecrew.io/docs/bc_aws_general_43#terraform" + }, + "Recommendation": { + "Text": "Use a Security policy with a ciphers that are stronger as possible. Drop legacy and unsecure ciphers.", + "Url": "https://docs.aws.amazon.com/elasticloadbalancing/latest/application/create-https-listener.html#describe-ssl-policies" + } + }, + "Categories": [], + "Tags": { + "Tag1Key": "value", + "Tag2Key": "value" + }, + "DependsOn": [], + "RelatedTo": [], + "Notes": "", + "Compliance": [] + } diff --git a/providers/aws/services/elb/elb_insecure_ssl_ciphers/elb_insecure_ssl_ciphers.py b/providers/aws/services/elb/elb_insecure_ssl_ciphers/elb_insecure_ssl_ciphers.py new file mode 100644 index 00000000..b359412a --- /dev/null +++ b/providers/aws/services/elb/elb_insecure_ssl_ciphers/elb_insecure_ssl_ciphers.py @@ -0,0 +1,28 @@ +from lib.check.models import Check, Check_Report +from providers.aws.services.elb.elb_client import elb_client + + +class elb_insecure_ssl_ciphers(Check): + def execute(self): + findings = [] + secure_ssl_policies = [ + "ELBSecurityPolicy-TLS-1-2-2017-01", + ] + for lb in elb_client.loadbalancers: + report = Check_Report(self.metadata) + report.region = lb.region + report.resource_id = lb.name + report.status = "PASS" + report.status_extended = ( + f"ELB {lb.name} has not insecure SSL protocols or ciphers." + ) + for listener in lb.listeners: + if listener.protocol == "HTTPS" and not any( + check in listener.policies for check in secure_ssl_policies + ): + report.status = "FAIL" + report.status_extended = f"ELB {lb.name} has listeners with insecure SSL protocols or ciphers." + + findings.append(report) + + return findings diff --git a/providers/aws/services/elb/elb_insecure_ssl_ciphers/elb_insecure_ssl_ciphers_test.py b/providers/aws/services/elb/elb_insecure_ssl_ciphers/elb_insecure_ssl_ciphers_test.py new file mode 100644 index 00000000..e4ebeaa3 --- /dev/null +++ b/providers/aws/services/elb/elb_insecure_ssl_ciphers/elb_insecure_ssl_ciphers_test.py @@ -0,0 +1,129 @@ +from re import search +from unittest import mock + +from boto3 import client, resource +from moto import mock_ec2, mock_elb + +AWS_REGION = "eu-west-1" +AWS_ACCOUNT_NUMBER = "123456789012" + + +class Test_elb_insecure_ssl_ciphers: + @mock_elb + def test_elb_no_balancers(self): + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elb.elb_service import ELB + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elb.elb_insecure_ssl_ciphers.elb_insecure_ssl_ciphers.elb_client", + new=ELB(current_audit_info), + ): + # Test Check + from providers.aws.services.elb.elb_insecure_ssl_ciphers.elb_insecure_ssl_ciphers import ( + elb_insecure_ssl_ciphers, + ) + + check = elb_insecure_ssl_ciphers() + result = check.execute() + + assert len(result) == 0 + + @mock_ec2 + @mock_elb + def test_elb_listener_with_secure_policy(self): + elb = client("elb", region_name=AWS_REGION) + ec2 = resource("ec2", region_name=AWS_REGION) + + security_group = ec2.create_security_group( + GroupName="sg01", Description="Test security group sg01" + ) + + elb.create_load_balancer( + LoadBalancerName="my-lb", + Listeners=[ + {"Protocol": "tcp", "LoadBalancerPort": 80, "InstancePort": 8080}, + {"Protocol": "https", "LoadBalancerPort": 443, "InstancePort": 9000}, + ], + AvailabilityZones=[f"{AWS_REGION}a"], + Scheme="internal", + SecurityGroups=[security_group.id], + ) + + elb.set_load_balancer_policies_of_listener( + LoadBalancerName="my-lb", + LoadBalancerPort=443, + PolicyNames=["ELBSecurityPolicy-TLS-1-2-2017-01"], + ) + elb.describe_load_balancer_policies(LoadBalancerName="my-lb") + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elb.elb_service import ELB + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elb.elb_insecure_ssl_ciphers.elb_insecure_ssl_ciphers.elb_client", + new=ELB(current_audit_info), + ): + from providers.aws.services.elb.elb_insecure_ssl_ciphers.elb_insecure_ssl_ciphers import ( + elb_insecure_ssl_ciphers, + ) + + check = elb_insecure_ssl_ciphers() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert search( + "has not insecure SSL protocols or ciphers", + result[0].status_extended, + ) + assert result[0].resource_id == "my-lb" + + @mock_ec2 + @mock_elb + def test_elb_with_HTTPS_listener(self): + elb = client("elb", region_name=AWS_REGION) + ec2 = resource("ec2", region_name=AWS_REGION) + + security_group = ec2.create_security_group( + GroupName="sg01", Description="Test security group sg01" + ) + + elb.create_load_balancer( + LoadBalancerName="my-lb", + Listeners=[ + {"Protocol": "tcp", "LoadBalancerPort": 80, "InstancePort": 8080}, + {"Protocol": "https", "LoadBalancerPort": 443, "InstancePort": 9000}, + ], + AvailabilityZones=[f"{AWS_REGION}a"], + Scheme="internal", + SecurityGroups=[security_group.id], + ) + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elb.elb_service import ELB + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elb.elb_insecure_ssl_ciphers.elb_insecure_ssl_ciphers.elb_client", + new=ELB(current_audit_info), + ): + from providers.aws.services.elb.elb_insecure_ssl_ciphers.elb_insecure_ssl_ciphers import ( + elb_insecure_ssl_ciphers, + ) + + check = elb_insecure_ssl_ciphers() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert search( + "has listeners with insecure SSL protocols or ciphers", + result[0].status_extended, + ) + assert result[0].resource_id == "my-lb" diff --git a/providers/aws/services/elb/elb_internet_facing/__init__.py b/providers/aws/services/elb/elb_internet_facing/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/providers/aws/services/elb/elb_internet_facing/elb_internet_facing.metadata.json b/providers/aws/services/elb/elb_internet_facing/elb_internet_facing.metadata.json new file mode 100644 index 00000000..50d9de4a --- /dev/null +++ b/providers/aws/services/elb/elb_internet_facing/elb_internet_facing.metadata.json @@ -0,0 +1,35 @@ +{ + "Provider": "aws", + "CheckID": "elb_internet_facing", + "CheckTitle": "Check for internet facing Elastic Load Balancers.", + "CheckType": ["Data Protection"], + "ServiceName": "elb", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "Severity": "medium", + "ResourceType": "AwsElbLoadBalancer", + "Description": "Check for internet facing Elastic Load Balancers.", + "Risk": "Publicly accessible load balancers could expose sensitive data to bad actors.", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/ELB/internet-facing-load-balancers.html", + "Terraform": "" + }, + "Recommendation": { + "Text": "Ensure the load balancer should be publicly accessible. If publicly exposed ensure a WAF ACL is implemented.", + "Url": "https://docs.aws.amazon.com/waf/latest/developerguide/web-acl-associating-aws-resource.html" + } + }, + "Categories": [], + "Tags": { + "Tag1Key": "value", + "Tag2Key": "value" + }, + "DependsOn": [], + "RelatedTo": [], + "Notes": "", + "Compliance": [] + } diff --git a/providers/aws/services/elb/elb_internet_facing/elb_internet_facing.py b/providers/aws/services/elb/elb_internet_facing/elb_internet_facing.py new file mode 100644 index 00000000..e0fe0cf5 --- /dev/null +++ b/providers/aws/services/elb/elb_internet_facing/elb_internet_facing.py @@ -0,0 +1,22 @@ +from lib.check.models import Check, Check_Report +from providers.aws.services.elb.elb_client import elb_client + + +class elb_internet_facing(Check): + def execute(self): + findings = [] + for lb in elb_client.loadbalancers: + report = Check_Report(self.metadata) + report.region = lb.region + report.resource_id = lb.name + report.status = "PASS" + report.status_extended = f"ELB {lb.name} is not internet facing." + if lb.scheme == "internet-facing": + report.status = "FAIL" + report.status_extended = ( + f"ELB {lb.name} is internet facing in {lb.dns}." + ) + + findings.append(report) + + return findings diff --git a/providers/aws/services/elb/elb_internet_facing/elb_internet_facing_test.py b/providers/aws/services/elb/elb_internet_facing/elb_internet_facing_test.py new file mode 100644 index 00000000..118b0226 --- /dev/null +++ b/providers/aws/services/elb/elb_internet_facing/elb_internet_facing_test.py @@ -0,0 +1,122 @@ +from re import search +from unittest import mock + +from boto3 import client, resource +from moto import mock_ec2, mock_elb + +AWS_REGION = "eu-west-1" +AWS_ACCOUNT_NUMBER = "123456789012" + + +class Test_elb_request_smugling: + @mock_elb + def test_elb_no_balancers(self): + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elb.elb_service import ELB + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elb.elb_internet_facing.elb_internet_facing.elb_client", + new=ELB(current_audit_info), + ): + # Test Check + from providers.aws.services.elb.elb_internet_facing.elb_internet_facing import ( + elb_internet_facing, + ) + + check = elb_internet_facing() + result = check.execute() + + assert len(result) == 0 + + @mock_ec2 + @mock_elb + def test_elb_private(self): + elb = client("elb", region_name=AWS_REGION) + ec2 = resource("ec2", region_name=AWS_REGION) + + security_group = ec2.create_security_group( + GroupName="sg01", Description="Test security group sg01" + ) + + elb.create_load_balancer( + LoadBalancerName="my-lb", + Listeners=[ + {"Protocol": "tcp", "LoadBalancerPort": 80, "InstancePort": 8080}, + {"Protocol": "http", "LoadBalancerPort": 81, "InstancePort": 9000}, + ], + AvailabilityZones=[f"{AWS_REGION}a"], + Scheme="internal", + SecurityGroups=[security_group.id], + ) + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elb.elb_service import ELB + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elb.elb_internet_facing.elb_internet_facing.elb_client", + new=ELB(current_audit_info), + ): + from providers.aws.services.elb.elb_internet_facing.elb_internet_facing import ( + elb_internet_facing, + ) + + check = elb_internet_facing() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert search( + "is not internet facing", + result[0].status_extended, + ) + assert result[0].resource_id == "my-lb" + + @mock_ec2 + @mock_elb + def test_elb_with_deletion_protection(self): + elb = client("elb", region_name=AWS_REGION) + ec2 = resource("ec2", region_name=AWS_REGION) + + security_group = ec2.create_security_group( + GroupName="sg01", Description="Test security group sg01" + ) + + elb.create_load_balancer( + LoadBalancerName="my-lb", + Listeners=[ + {"Protocol": "tcp", "LoadBalancerPort": 80, "InstancePort": 8080}, + {"Protocol": "http", "LoadBalancerPort": 81, "InstancePort": 9000}, + ], + AvailabilityZones=[f"{AWS_REGION}a"], + Scheme="internet-facing", + SecurityGroups=[security_group.id], + ) + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elb.elb_service import ELB + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elb.elb_internet_facing.elb_internet_facing.elb_client", + new=ELB(current_audit_info), + ): + from providers.aws.services.elb.elb_internet_facing.elb_internet_facing import ( + elb_internet_facing, + ) + + check = elb_internet_facing() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert search( + "is internet facing", + result[0].status_extended, + ) + assert result[0].resource_id == "my-lb" diff --git a/providers/aws/services/elb/elb_logging_enabled/__init__.py b/providers/aws/services/elb/elb_logging_enabled/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/providers/aws/services/elb/elb_logging_enabled/elb_logging_enabled.metadata.json b/providers/aws/services/elb/elb_logging_enabled/elb_logging_enabled.metadata.json new file mode 100644 index 00000000..e8dd1630 --- /dev/null +++ b/providers/aws/services/elb/elb_logging_enabled/elb_logging_enabled.metadata.json @@ -0,0 +1,35 @@ +{ + "Provider": "aws", + "CheckID": "elb_logging_enabled", + "CheckTitle": "Check if Elastic Load Balancers have logging enabled.", + "CheckType": ["Logging and Monitoring"], + "ServiceName": "elb", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "Severity": "medium", + "ResourceType": "AwsElbLoadBalancer", + "Description": "Check if Elastic Load Balancers have logging enabled.", + "Risk": "If logs are not enabled monitoring of service use and threat analysis is not possible.", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "aws elb modify-load-balancer-attributes --load-balancer-name --load-balancer-attributes '{AccessLog:{Enabled:true,EmitInterval:60,S3BucketName:}}'", + "NativeIaC": "https://docs.bridgecrew.io/docs/bc_aws_logging_23#cloudformation", + "Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/ELB/elb-access-log.html", + "Terraform": "https://docs.bridgecrew.io/docs/bc_aws_logging_23#terraform" + }, + "Recommendation": { + "Text": "Enable ELB logging, create la log lifecycle and define use cases.", + "Url": "https://docs.aws.amazon.com/elasticloadbalancing/latest/classic/access-log-collection.html" + } + }, + "Categories": [], + "Tags": { + "Tag1Key": "value", + "Tag2Key": "value" + }, + "DependsOn": [], + "RelatedTo": [], + "Notes": "", + "Compliance": [] + } diff --git a/providers/aws/services/elb/elb_logging_enabled/elb_logging_enabled.py b/providers/aws/services/elb/elb_logging_enabled/elb_logging_enabled.py new file mode 100644 index 00000000..27f72bd9 --- /dev/null +++ b/providers/aws/services/elb/elb_logging_enabled/elb_logging_enabled.py @@ -0,0 +1,22 @@ +from lib.check.models import Check, Check_Report +from providers.aws.services.elb.elb_client import elb_client + + +class elb_logging_enabled(Check): + def execute(self): + findings = [] + for lb in elb_client.loadbalancers: + report = Check_Report(self.metadata) + report.region = lb.region + report.resource_id = lb.name + report.status = "FAIL" + report.status_extended = f"ELB {lb.name} has not configured access logs." + if lb.access_logs: + report.status = "PASS" + report.status_extended = ( + f"ELB {lb.name} has access logs to S3 configured." + ) + + findings.append(report) + + return findings diff --git a/providers/aws/services/elb/elb_logging_enabled/elb_logging_enabled_test.py b/providers/aws/services/elb/elb_logging_enabled/elb_logging_enabled_test.py new file mode 100644 index 00000000..7bb89c9e --- /dev/null +++ b/providers/aws/services/elb/elb_logging_enabled/elb_logging_enabled_test.py @@ -0,0 +1,134 @@ +from re import search +from unittest import mock + +from boto3 import client, resource +from moto import mock_ec2, mock_elb + +AWS_REGION = "eu-west-1" +AWS_ACCOUNT_NUMBER = "123456789012" + + +class Test_elb_logging_enabled: + @mock_elb + def test_elb_no_balancers(self): + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elb.elb_service import ELB + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elb.elb_logging_enabled.elb_logging_enabled.elb_client", + new=ELB(current_audit_info), + ): + # Test Check + from providers.aws.services.elb.elb_logging_enabled.elb_logging_enabled import ( + elb_logging_enabled, + ) + + check = elb_logging_enabled() + result = check.execute() + + assert len(result) == 0 + + @mock_ec2 + @mock_elb + def test_elb_without_access_log(self): + elb = client("elb", region_name=AWS_REGION) + ec2 = resource("ec2", region_name=AWS_REGION) + + security_group = ec2.create_security_group( + GroupName="sg01", Description="Test security group sg01" + ) + + elb.create_load_balancer( + LoadBalancerName="my-lb", + Listeners=[ + {"Protocol": "tcp", "LoadBalancerPort": 80, "InstancePort": 8080}, + {"Protocol": "http", "LoadBalancerPort": 81, "InstancePort": 9000}, + ], + AvailabilityZones=[f"{AWS_REGION}a"], + Scheme="internal", + SecurityGroups=[security_group.id], + ) + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elb.elb_service import ELB + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elb.elb_logging_enabled.elb_logging_enabled.elb_client", + new=ELB(current_audit_info), + ): + from providers.aws.services.elb.elb_logging_enabled.elb_logging_enabled import ( + elb_logging_enabled, + ) + + check = elb_logging_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert search( + "has not configured access logs", + result[0].status_extended, + ) + assert result[0].resource_id == "my-lb" + + @mock_ec2 + @mock_elb + def test_elb_with_deletion_protection(self): + elb = client("elb", region_name=AWS_REGION) + ec2 = resource("ec2", region_name=AWS_REGION) + + security_group = ec2.create_security_group( + GroupName="sg01", Description="Test security group sg01" + ) + + elb.create_load_balancer( + LoadBalancerName="my-lb", + Listeners=[ + {"Protocol": "tcp", "LoadBalancerPort": 80, "InstancePort": 8080}, + {"Protocol": "http", "LoadBalancerPort": 81, "InstancePort": 9000}, + ], + AvailabilityZones=[f"{AWS_REGION}a"], + Scheme="internal", + SecurityGroups=[security_group.id], + ) + + elb.modify_load_balancer_attributes( + LoadBalancerName="my-lb", + LoadBalancerAttributes={ + "AccessLog": { + "Enabled": True, + "S3BucketName": "mb", + "EmitInterval": 42, + "S3BucketPrefix": "s3bf", + } + }, + ) + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elb.elb_service import ELB + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elb.elb_logging_enabled.elb_logging_enabled.elb_client", + new=ELB(current_audit_info), + ): + from providers.aws.services.elb.elb_logging_enabled.elb_logging_enabled import ( + elb_logging_enabled, + ) + + check = elb_logging_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert search( + "has access logs to S3 configured", + result[0].status_extended, + ) + assert result[0].resource_id == "my-lb" diff --git a/providers/aws/services/elb/elb_service.py b/providers/aws/services/elb/elb_service.py new file mode 100644 index 00000000..8b1e15af --- /dev/null +++ b/providers/aws/services/elb/elb_service.py @@ -0,0 +1,91 @@ +import threading +from typing import Optional + +from pydantic import BaseModel + +from lib.logger import logger +from providers.aws.aws_provider import generate_regional_clients + + +################### ELB +class ELB: + def __init__(self, audit_info): + self.service = "elb" + self.session = audit_info.audit_session + self.regional_clients = generate_regional_clients(self.service, audit_info) + self.loadbalancers = [] + self.__threading_call__(self.__describe_load_balancers__) + self.__threading_call__(self.__describe_load_balancer_attributes__) + + def __get_session__(self): + return self.session + + def __threading_call__(self, call): + threads = [] + for regional_client in self.regional_clients.values(): + threads.append(threading.Thread(target=call, args=(regional_client,))) + for t in threads: + t.start() + for t in threads: + t.join() + + def __describe_load_balancers__(self, regional_client): + logger.info("ELB - Describing load balancers...") + try: + describe_elb_paginator = regional_client.get_paginator( + "describe_load_balancers" + ) + for page in describe_elb_paginator.paginate(): + for elb in page["LoadBalancerDescriptions"]: + listeners = [] + for listener in elb["ListenerDescriptions"]: + listeners.append( + Listener( + protocol=listener["Listener"]["Protocol"], + policies=listener["PolicyNames"], + ) + ) + self.loadbalancers.append( + LoadBalancer( + name=elb["LoadBalancerName"], + dns=elb["DNSName"], + region=regional_client.region, + scheme=elb["Scheme"], + listeners=listeners, + ) + ) + + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + def __describe_load_balancer_attributes__(self, regional_client): + logger.info("ELB - Describing attributes...") + try: + for lb in self.loadbalancers: + if lb.region == regional_client.region: + attributes = regional_client.describe_load_balancer_attributes( + LoadBalancerName=lb.name + )["LoadBalancerAttributes"] + if "AccessLog" in attributes: + lb.access_logs = attributes["AccessLog"]["Enabled"] + + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + +class Listener(BaseModel): + protocol: str + policies: list[str] + + +class LoadBalancer(BaseModel): + name: str + dns: str + region: str + scheme: str + access_logs: Optional[bool] + listeners: list[Listener] diff --git a/providers/aws/services/elb/elb_service_test.py b/providers/aws/services/elb/elb_service_test.py new file mode 100644 index 00000000..78c96693 --- /dev/null +++ b/providers/aws/services/elb/elb_service_test.py @@ -0,0 +1,126 @@ +from boto3 import client, resource, session +from moto import mock_ec2, mock_elb + +from providers.aws.lib.audit_info.models import AWS_Audit_Info +from providers.aws.services.elb.elb_service import ELB + +AWS_ACCOUNT_NUMBER = 123456789012 +AWS_REGION = "us-east-1" + + +class Test_ELB_Service: + # Mocked Audit Info + def set_mocked_audit_info(self): + audit_info = AWS_Audit_Info( + 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=None, + organizations_metadata=None, + ) + return audit_info + + # Test ELB Service + @mock_elb + def test_service(self): + # ELB client for this test class + audit_info = self.set_mocked_audit_info() + elb = ELB(audit_info) + assert elb.service == "elb" + + # Test ELB Client + @mock_elb + def test_client(self): + # ELB client for this test class + audit_info = self.set_mocked_audit_info() + elb = ELB(audit_info) + for regional_client in elb.regional_clients.values(): + assert regional_client.__class__.__name__ == "ElasticLoadBalancing" + + # Test ELB Session + @mock_elb + def test__get_session__(self): + # ELB client for this test class + audit_info = self.set_mocked_audit_info() + elb = ELB(audit_info) + assert elb.session.__class__.__name__ == "Session" + + # Test ELB Describe Load Balancers + @mock_ec2 + @mock_elb + def test__describe_load_balancers__(self): + elb = client("elb", region_name=AWS_REGION) + ec2 = resource("ec2", region_name=AWS_REGION) + + security_group = ec2.create_security_group( + GroupName="sg01", Description="Test security group sg01" + ) + + elb.create_load_balancer( + LoadBalancerName="my-lb", + Listeners=[ + {"Protocol": "tcp", "LoadBalancerPort": 80, "InstancePort": 8080}, + {"Protocol": "http", "LoadBalancerPort": 81, "InstancePort": 9000}, + ], + AvailabilityZones=[f"{AWS_REGION}a"], + Scheme="internal", + SecurityGroups=[security_group.id], + ) + # ELB client for this test class + audit_info = self.set_mocked_audit_info() + elb = ELB(audit_info) + assert len(elb.loadbalancers) == 1 + assert elb.loadbalancers[0].name == "my-lb" + assert elb.loadbalancers[0].region == AWS_REGION + assert elb.loadbalancers[0].scheme == "internal" + + # Test ELB Describe Load Balancers Attributes + @mock_ec2 + @mock_elb + def test__describe_load_balancer_attributes__(self): + elb = client("elb", region_name=AWS_REGION) + ec2 = resource("ec2", region_name=AWS_REGION) + + security_group = ec2.create_security_group( + GroupName="sg01", Description="Test security group sg01" + ) + + elb.create_load_balancer( + LoadBalancerName="my-lb", + Listeners=[ + {"Protocol": "tcp", "LoadBalancerPort": 80, "InstancePort": 8080}, + {"Protocol": "http", "LoadBalancerPort": 81, "InstancePort": 9000}, + ], + AvailabilityZones=[f"{AWS_REGION}a"], + Scheme="internal", + SecurityGroups=[security_group.id], + ) + + elb.modify_load_balancer_attributes( + LoadBalancerName="my-lb", + LoadBalancerAttributes={ + "AccessLog": { + "Enabled": True, + "S3BucketName": "mb", + "EmitInterval": 42, + "S3BucketPrefix": "s3bf", + } + }, + ) + # ELB client for this test class + audit_info = self.set_mocked_audit_info() + elb = ELB(audit_info) + assert elb.loadbalancers[0].name == "my-lb" + assert elb.loadbalancers[0].region == AWS_REGION + assert elb.loadbalancers[0].scheme == "internal" + assert elb.loadbalancers[0].access_logs diff --git a/providers/aws/services/elb/elb_ssl_listeners/__init__.py b/providers/aws/services/elb/elb_ssl_listeners/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/providers/aws/services/elb/elb_ssl_listeners/elb_ssl_listeners.metadata.json b/providers/aws/services/elb/elb_ssl_listeners/elb_ssl_listeners.metadata.json new file mode 100644 index 00000000..aec1630e --- /dev/null +++ b/providers/aws/services/elb/elb_ssl_listeners/elb_ssl_listeners.metadata.json @@ -0,0 +1,35 @@ +{ + "Provider": "aws", + "CheckID": "elb_ssl_listeners", + "CheckTitle": "Check if Elastic Load Balancers have SSL listeners.", + "CheckType": ["Data Protection"], + "ServiceName": "elb", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "Severity": "medium", + "ResourceType": "AwsElbLoadBalancer", + "Description": "Check if Elastic Load Balancers have SSL listeners.", + "Risk": "Clear text communication could affect privacy of information in transit.", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "aws elb create-load-balancer-listeners --load-balancer-name --listeners Protocol=HTTPS, LoadBalancerPort=443, InstanceProtocol=HTTP, InstancePort=80, SSLCertificateId=", + "NativeIaC": "", + "Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/ELB/elb-listener-security.html", + "Terraform": "" + }, + "Recommendation": { + "Text": "Scan for Load Balancers with HTTP or TCP listeners and understand the reason for each of them. Check if the listener can be implemented as TLS instead..", + "Url": "https://docs.aws.amazon.com/elasticloadbalancing/latest/application/create-https-listener.html" + } + }, + "Categories": [], + "Tags": { + "Tag1Key": "value", + "Tag2Key": "value" + }, + "DependsOn": [], + "RelatedTo": [], + "Notes": "", + "Compliance": [] + } diff --git a/providers/aws/services/elb/elb_ssl_listeners/elb_ssl_listeners.py b/providers/aws/services/elb/elb_ssl_listeners/elb_ssl_listeners.py new file mode 100644 index 00000000..f6a72d09 --- /dev/null +++ b/providers/aws/services/elb/elb_ssl_listeners/elb_ssl_listeners.py @@ -0,0 +1,24 @@ +from lib.check.models import Check, Check_Report +from providers.aws.services.elb.elb_client import elb_client + + +class elb_ssl_listeners(Check): + def execute(self): + findings = [] + secure_protocols = ["SSL", "HTTPS"] + for lb in elb_client.loadbalancers: + report = Check_Report(self.metadata) + report.region = lb.region + report.resource_id = lb.name + report.status = "PASS" + report.status_extended = f"ELB {lb.name} has HTTPS listeners only." + for listener in lb.listeners: + if listener.protocol not in secure_protocols: + report.status = "FAIL" + report.status_extended = ( + f"ELB {lb.name} has non-encrypted listeners." + ) + + findings.append(report) + + return findings diff --git a/providers/aws/services/elb/elb_ssl_listeners/elb_ssl_listeners_test.py b/providers/aws/services/elb/elb_ssl_listeners/elb_ssl_listeners_test.py new file mode 100644 index 00000000..d468dadd --- /dev/null +++ b/providers/aws/services/elb/elb_ssl_listeners/elb_ssl_listeners_test.py @@ -0,0 +1,120 @@ +from re import search +from unittest import mock + +from boto3 import client, resource +from moto import mock_ec2, mock_elb + +AWS_REGION = "eu-west-1" +AWS_ACCOUNT_NUMBER = "123456789012" + + +class Test_elb_ssl_listeners: + @mock_elb + def test_elb_no_balancers(self): + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elb.elb_service import ELB + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elb.elb_ssl_listeners.elb_ssl_listeners.elb_client", + new=ELB(current_audit_info), + ): + # Test Check + from providers.aws.services.elb.elb_ssl_listeners.elb_ssl_listeners import ( + elb_ssl_listeners, + ) + + check = elb_ssl_listeners() + result = check.execute() + + assert len(result) == 0 + + @mock_ec2 + @mock_elb + def test_elb_with_HTTP_listener(self): + elb = client("elb", region_name=AWS_REGION) + ec2 = resource("ec2", region_name=AWS_REGION) + + security_group = ec2.create_security_group( + GroupName="sg01", Description="Test security group sg01" + ) + + elb.create_load_balancer( + LoadBalancerName="my-lb", + Listeners=[ + {"Protocol": "tcp", "LoadBalancerPort": 80, "InstancePort": 8080}, + {"Protocol": "http", "LoadBalancerPort": 81, "InstancePort": 9000}, + ], + AvailabilityZones=[f"{AWS_REGION}a"], + Scheme="internal", + SecurityGroups=[security_group.id], + ) + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elb.elb_service import ELB + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elb.elb_ssl_listeners.elb_ssl_listeners.elb_client", + new=ELB(current_audit_info), + ): + from providers.aws.services.elb.elb_ssl_listeners.elb_ssl_listeners import ( + elb_ssl_listeners, + ) + + check = elb_ssl_listeners() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert search( + "has non-encrypted listeners", + result[0].status_extended, + ) + assert result[0].resource_id == "my-lb" + + @mock_ec2 + @mock_elb + def test_elb_with_HTTPS_listener(self): + elb = client("elb", region_name=AWS_REGION) + ec2 = resource("ec2", region_name=AWS_REGION) + + security_group = ec2.create_security_group( + GroupName="sg01", Description="Test security group sg01" + ) + + elb.create_load_balancer( + LoadBalancerName="my-lb", + Listeners=[ + {"Protocol": "https", "LoadBalancerPort": 443, "InstancePort": 9000}, + ], + AvailabilityZones=[f"{AWS_REGION}a"], + Scheme="internal", + SecurityGroups=[security_group.id], + ) + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elb.elb_service import ELB + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elb.elb_ssl_listeners.elb_ssl_listeners.elb_client", + new=ELB(current_audit_info), + ): + from providers.aws.services.elb.elb_ssl_listeners.elb_ssl_listeners import ( + elb_ssl_listeners, + ) + + check = elb_ssl_listeners() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert search( + "has HTTPS listeners only", + result[0].status_extended, + ) + assert result[0].resource_id == "my-lb" diff --git a/providers/aws/services/elbv2/__init__.py b/providers/aws/services/elbv2/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/providers/aws/services/elbv2/elbv2_client.py b/providers/aws/services/elbv2/elbv2_client.py new file mode 100644 index 00000000..fd544dcc --- /dev/null +++ b/providers/aws/services/elbv2/elbv2_client.py @@ -0,0 +1,4 @@ +from providers.aws.lib.audit_info.audit_info import current_audit_info +from providers.aws.services.elbv2.elbv2_service import ELBv2 + +elbv2_client = ELBv2(current_audit_info) diff --git a/providers/aws/services/elbv2/elbv2_deletion_protection/__init__.py b/providers/aws/services/elbv2/elbv2_deletion_protection/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/providers/aws/services/elbv2/elbv2_deletion_protection/elbv2_deletion_protection.metadata.json b/providers/aws/services/elbv2/elbv2_deletion_protection/elbv2_deletion_protection.metadata.json new file mode 100644 index 00000000..edcb0e95 --- /dev/null +++ b/providers/aws/services/elbv2/elbv2_deletion_protection/elbv2_deletion_protection.metadata.json @@ -0,0 +1,35 @@ +{ + "Provider": "aws", + "CheckID": "elbv2_deletion_protection", + "CheckTitle": "Check if Elastic Load Balancers have deletion protection enabled.", + "CheckType": ["Data Protection"], + "ServiceName": "elbv2", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "Severity": "medium", + "ResourceType": "AwsElasticLoadBalancingV2LoadBalancer", + "Description": "Check if Elastic Load Balancers have deletion protection enabled.", + "Risk": "If deletion protection is not enabled, the resource is not protected against deletion.", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "aws elbv2 modify-load-balancer-attributes --load-balancer-arn --attributes Key=deletion_protection.enabled,Value=true", + "NativeIaC": "", + "Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/ELBv2/deletion-protection.html", + "Terraform": "https://docs.bridgecrew.io/docs/bc_aws_networking_62#terraform" + }, + "Recommendation": { + "Text": "Enable deletion protection attribute, this is not enabled by default.", + "Url": "https://docs.aws.amazon.com/elasticloadbalancing/latest/application/application-load-balancers.html#deletion-protection" + } + }, + "Categories": [], + "Tags": { + "Tag1Key": "value", + "Tag2Key": "value" + }, + "DependsOn": [], + "RelatedTo": [], + "Notes": "", + "Compliance": [] + } diff --git a/providers/aws/services/elbv2/elbv2_deletion_protection/elbv2_deletion_protection.py b/providers/aws/services/elbv2/elbv2_deletion_protection/elbv2_deletion_protection.py new file mode 100644 index 00000000..223fac05 --- /dev/null +++ b/providers/aws/services/elbv2/elbv2_deletion_protection/elbv2_deletion_protection.py @@ -0,0 +1,23 @@ +from lib.check.models import Check, Check_Report +from providers.aws.services.elbv2.elbv2_client import elbv2_client + + +class elbv2_deletion_protection(Check): + def execute(self): + findings = [] + for lb in elbv2_client.loadbalancersv2: + report = Check_Report(self.metadata) + report.region = lb.region + report.resource_id = lb.name + report.resource_arn = lb.arn + report.status = "FAIL" + report.status_extended = f"ELBv2 {lb.name} has not deletion protection." + if lb.deletion_protection == "true": + report.status = "PASS" + report.status_extended = ( + f"ELBv2 {lb.name} has deletion protection enabled." + ) + + findings.append(report) + + return findings diff --git a/providers/aws/services/elbv2/elbv2_deletion_protection/elbv2_deletion_protection_test.py b/providers/aws/services/elbv2/elbv2_deletion_protection/elbv2_deletion_protection_test.py new file mode 100644 index 00000000..6809dbac --- /dev/null +++ b/providers/aws/services/elbv2/elbv2_deletion_protection/elbv2_deletion_protection_test.py @@ -0,0 +1,145 @@ +from re import search +from unittest import mock + +from boto3 import client, resource +from moto import mock_ec2, mock_elbv2 + +AWS_REGION = "eu-west-1" +AWS_ACCOUNT_NUMBER = "123456789012" + + +class Test_elbv2_deletion_protection: + @mock_elbv2 + def test_elb_no_balancers(self): + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elbv2.elbv2_service import ELBv2 + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elbv2.elbv2_deletion_protection.elbv2_deletion_protection.elbv2_client", + new=ELBv2(current_audit_info), + ): + # Test Check + from providers.aws.services.elbv2.elbv2_deletion_protection.elbv2_deletion_protection import ( + elbv2_deletion_protection, + ) + + check = elbv2_deletion_protection() + result = check.execute() + + assert len(result) == 0 + + @mock_ec2 + @mock_elbv2 + def test_elbv2_without_deletion_protection(self): + conn = client("elbv2", region_name=AWS_REGION) + ec2 = resource("ec2", region_name=AWS_REGION) + + security_group = ec2.create_security_group( + GroupName="a-security-group", Description="First One" + ) + vpc = ec2.create_vpc(CidrBlock="172.28.7.0/24", InstanceTenancy="default") + subnet1 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.192/26", AvailabilityZone=f"{AWS_REGION}a" + ) + subnet2 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.0/26", AvailabilityZone=f"{AWS_REGION}b" + ) + + lb = conn.create_load_balancer( + Name="my-lb", + Subnets=[subnet1.id, subnet2.id], + SecurityGroups=[security_group.id], + Scheme="internal", + Type="application", + )["LoadBalancers"][0] + + conn.modify_load_balancer_attributes( + LoadBalancerArn=lb["LoadBalancerArn"], + Attributes=[ + {"Key": "deletion_protection.enabled", "Value": "false"}, + ], + ) + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elbv2.elbv2_service import ELBv2 + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elbv2.elbv2_deletion_protection.elbv2_deletion_protection.elbv2_client", + new=ELBv2(current_audit_info), + ): + from providers.aws.services.elbv2.elbv2_deletion_protection.elbv2_deletion_protection import ( + elbv2_deletion_protection, + ) + + check = elbv2_deletion_protection() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert search( + "has not deletion protection", + result[0].status_extended, + ) + assert result[0].resource_id == "my-lb" + assert result[0].resource_arn == lb["LoadBalancerArn"] + + @mock_ec2 + @mock_elbv2 + def test_elbv2_with_deletion_protection(self): + conn = client("elbv2", region_name=AWS_REGION) + ec2 = resource("ec2", region_name=AWS_REGION) + + security_group = ec2.create_security_group( + GroupName="a-security-group", Description="First One" + ) + vpc = ec2.create_vpc(CidrBlock="172.28.7.0/24", InstanceTenancy="default") + subnet1 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.192/26", AvailabilityZone=f"{AWS_REGION}a" + ) + subnet2 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.0/26", AvailabilityZone=f"{AWS_REGION}b" + ) + + lb = conn.create_load_balancer( + Name="my-lb", + Subnets=[subnet1.id, subnet2.id], + SecurityGroups=[security_group.id], + Scheme="internal", + )["LoadBalancers"][0] + + conn.modify_load_balancer_attributes( + LoadBalancerArn=lb["LoadBalancerArn"], + Attributes=[ + {"Key": "deletion_protection.enabled", "Value": "true"}, + ], + ) + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elbv2.elbv2_service import ELBv2 + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elbv2.elbv2_deletion_protection.elbv2_deletion_protection.elbv2_client", + new=ELBv2(current_audit_info), + ): + from providers.aws.services.elbv2.elbv2_deletion_protection.elbv2_deletion_protection import ( + elbv2_deletion_protection, + ) + + check = elbv2_deletion_protection() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert search( + "has deletion protection enabled", + result[0].status_extended, + ) + assert result[0].resource_id == "my-lb" + assert result[0].resource_arn == lb["LoadBalancerArn"] diff --git a/providers/aws/services/elbv2/elbv2_desync_mitigation_mode/__init__.py b/providers/aws/services/elbv2/elbv2_desync_mitigation_mode/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/providers/aws/services/elbv2/elbv2_desync_mitigation_mode/elbv2_desync_mitigation_mode.metadata.json b/providers/aws/services/elbv2/elbv2_desync_mitigation_mode/elbv2_desync_mitigation_mode.metadata.json new file mode 100644 index 00000000..ecc5fe07 --- /dev/null +++ b/providers/aws/services/elbv2/elbv2_desync_mitigation_mode/elbv2_desync_mitigation_mode.metadata.json @@ -0,0 +1,35 @@ +{ + "Provider": "aws", + "CheckID": "elbv2_desync_mitigation_mode", + "CheckTitle": "Check whether the Application Load Balancer is configured with defensive or strictest desync mitigation mode.", + "CheckType": ["Data Protection"], + "ServiceName": "elbv2", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "Severity": "medium", + "ResourceType": "AwsElasticLoadBalancingV2LoadBalancer", + "Description": "Check whether the Application Load Balancer is configured with defensive or strictest desync mitigation mode.", + "Risk": "HTTP Desync issues can lead to request smuggling and make your applications vulnerable to request queue or cache poisoning; which could lead to credential hijacking or execution of unauthorized commands.", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "aws elbv2 modify-load-balancer-attributes --load-balancer-arn --attributes Key=routing.http.desync_mitigation_mode,Value=", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "Ensure Application Load Balancer is configured with defensive or strictest desync mitigation mode.", + "Url": "https://aws.amazon.com/about-aws/whats-new/2020/08/application-and-classic-load-balancers-adding-defense-in-depth-with-introduction-of-desync-mitigation-mode/" + } + }, + "Categories": [], + "Tags": { + "Tag1Key": "value", + "Tag2Key": "value" + }, + "DependsOn": [], + "RelatedTo": [], + "Notes": "", + "Compliance": [] + } diff --git a/providers/aws/services/elbv2/elbv2_desync_mitigation_mode/elbv2_desync_mitigation_mode.py b/providers/aws/services/elbv2/elbv2_desync_mitigation_mode/elbv2_desync_mitigation_mode.py new file mode 100644 index 00000000..ab0f0fd3 --- /dev/null +++ b/providers/aws/services/elbv2/elbv2_desync_mitigation_mode/elbv2_desync_mitigation_mode.py @@ -0,0 +1,22 @@ +from lib.check.models import Check, Check_Report +from providers.aws.services.elbv2.elbv2_client import elbv2_client + + +class elbv2_desync_mitigation_mode(Check): + def execute(self): + findings = [] + for lb in elbv2_client.loadbalancersv2: + if lb.type == "application": + report = Check_Report(self.metadata) + report.region = lb.region + report.resource_id = lb.name + report.resource_arn = lb.arn + report.status = "PASS" + report.status_extended = f"ELBv2 ALB {lb.name} is configured with correct desync mitigation mode." + if lb.desync_mitigation_mode == "monitor": + report.status = "FAIL" + report.status_extended = f"ELBv2 ALB {lb.name} does not have desync mitigation mode set as defensive or strictest." + + findings.append(report) + + return findings diff --git a/providers/aws/services/elbv2/elbv2_desync_mitigation_mode/elbv2_desync_mitigation_mode_test.py b/providers/aws/services/elbv2/elbv2_desync_mitigation_mode/elbv2_desync_mitigation_mode_test.py new file mode 100644 index 00000000..07453da8 --- /dev/null +++ b/providers/aws/services/elbv2/elbv2_desync_mitigation_mode/elbv2_desync_mitigation_mode_test.py @@ -0,0 +1,145 @@ +from re import search +from unittest import mock + +from boto3 import client, resource +from moto import mock_ec2, mock_elbv2 + +AWS_REGION = "eu-west-1" +AWS_ACCOUNT_NUMBER = "123456789012" + + +class Test_elbv2_desync_mitigation_mode: + @mock_elbv2 + def test_elb_no_balancers(self): + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elbv2.elbv2_service import ELBv2 + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elbv2.elbv2_desync_mitigation_mode.elbv2_desync_mitigation_mode.elbv2_client", + new=ELBv2(current_audit_info), + ): + # Test Check + from providers.aws.services.elbv2.elbv2_desync_mitigation_mode.elbv2_desync_mitigation_mode import ( + elbv2_desync_mitigation_mode, + ) + + check = elbv2_desync_mitigation_mode() + result = check.execute() + + assert len(result) == 0 + + @mock_ec2 + @mock_elbv2 + def test_elbv2_without_desync_mitigation_mode(self): + conn = client("elbv2", region_name=AWS_REGION) + ec2 = resource("ec2", region_name=AWS_REGION) + + security_group = ec2.create_security_group( + GroupName="a-security-group", Description="First One" + ) + vpc = ec2.create_vpc(CidrBlock="172.28.7.0/24", InstanceTenancy="default") + subnet1 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.192/26", AvailabilityZone=f"{AWS_REGION}a" + ) + subnet2 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.0/26", AvailabilityZone=f"{AWS_REGION}b" + ) + + lb = conn.create_load_balancer( + Name="my-lb", + Subnets=[subnet1.id, subnet2.id], + SecurityGroups=[security_group.id], + Scheme="internal", + Type="application", + )["LoadBalancers"][0] + + conn.modify_load_balancer_attributes( + LoadBalancerArn=lb["LoadBalancerArn"], + Attributes=[ + {"Key": "routing.http.desync_mitigation_mode", "Value": "monitor"}, + ], + ) + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elbv2.elbv2_service import ELBv2 + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elbv2.elbv2_desync_mitigation_mode.elbv2_desync_mitigation_mode.elbv2_client", + new=ELBv2(current_audit_info), + ): + from providers.aws.services.elbv2.elbv2_desync_mitigation_mode.elbv2_desync_mitigation_mode import ( + elbv2_desync_mitigation_mode, + ) + + check = elbv2_desync_mitigation_mode() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert search( + "does not have desync mitigation mode set as defensive or strictest", + result[0].status_extended, + ) + assert result[0].resource_id == "my-lb" + assert result[0].resource_arn == lb["LoadBalancerArn"] + + @mock_ec2 + @mock_elbv2 + def test_elbv2_with_desync_mitigation_mode(self): + conn = client("elbv2", region_name=AWS_REGION) + ec2 = resource("ec2", region_name=AWS_REGION) + + security_group = ec2.create_security_group( + GroupName="a-security-group", Description="First One" + ) + vpc = ec2.create_vpc(CidrBlock="172.28.7.0/24", InstanceTenancy="default") + subnet1 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.192/26", AvailabilityZone=f"{AWS_REGION}a" + ) + subnet2 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.0/26", AvailabilityZone=f"{AWS_REGION}b" + ) + + lb = conn.create_load_balancer( + Name="my-lb", + Subnets=[subnet1.id, subnet2.id], + SecurityGroups=[security_group.id], + Scheme="internal", + )["LoadBalancers"][0] + + conn.modify_load_balancer_attributes( + LoadBalancerArn=lb["LoadBalancerArn"], + Attributes=[ + {"Key": "routing.http.desync_mitigation_mode", "Value": "defensive"}, + ], + ) + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elbv2.elbv2_service import ELBv2 + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elbv2.elbv2_desync_mitigation_mode.elbv2_desync_mitigation_mode.elbv2_client", + new=ELBv2(current_audit_info), + ): + from providers.aws.services.elbv2.elbv2_desync_mitigation_mode.elbv2_desync_mitigation_mode import ( + elbv2_desync_mitigation_mode, + ) + + check = elbv2_desync_mitigation_mode() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert search( + "is configured with correct desync mitigation mode", + result[0].status_extended, + ) + assert result[0].resource_id == "my-lb" + assert result[0].resource_arn == lb["LoadBalancerArn"] diff --git a/providers/aws/services/elbv2/elbv2_insecure_ssl_ciphers/__init__.py b/providers/aws/services/elbv2/elbv2_insecure_ssl_ciphers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/providers/aws/services/elbv2/elbv2_insecure_ssl_ciphers/elbv2_insecure_ssl_ciphers.metadata.json b/providers/aws/services/elbv2/elbv2_insecure_ssl_ciphers/elbv2_insecure_ssl_ciphers.metadata.json new file mode 100644 index 00000000..cdd968cd --- /dev/null +++ b/providers/aws/services/elbv2/elbv2_insecure_ssl_ciphers/elbv2_insecure_ssl_ciphers.metadata.json @@ -0,0 +1,35 @@ +{ + "Provider": "aws", + "CheckID": "elbv2_insecure_ssl_ciphers", + "CheckTitle": "Check if Elastic Load Balancers have insecure SSL ciphers.", + "CheckType": ["Data Protection"], + "ServiceName": "elbv2", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "Severity": "medium", + "ResourceType": "AwsElasticLoadBalancingV2LoadBalancer", + "Description": "Check if Elastic Load Balancers have insecure SSL ciphers.", + "Risk": "Using insecure ciphers could affect privacy of in transit information.", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "aws elbv2 modify-listener --listener-arn --ssl-policy ELBSecurityPolicy-TLS13-1-2-Ext2-2021-06", + "NativeIaC": "", + "Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/ELBv2/security-policy.html", + "Terraform": "https://docs.bridgecrew.io/docs/bc_aws_general_43#terraform" + }, + "Recommendation": { + "Text": "Use a Security policy with a ciphers that are stronger as possible. Drop legacy and unsecure ciphers.", + "Url": "https://docs.aws.amazon.com/elasticloadbalancing/latest/application/create-https-listener.html#describe-ssl-policies" + } + }, + "Categories": [], + "Tags": { + "Tag1Key": "value", + "Tag2Key": "value" + }, + "DependsOn": [], + "RelatedTo": [], + "Notes": "", + "Compliance": [] + } diff --git a/providers/aws/services/elbv2/elbv2_insecure_ssl_ciphers/elbv2_insecure_ssl_ciphers.py b/providers/aws/services/elbv2/elbv2_insecure_ssl_ciphers/elbv2_insecure_ssl_ciphers.py new file mode 100644 index 00000000..86e1a390 --- /dev/null +++ b/providers/aws/services/elbv2/elbv2_insecure_ssl_ciphers/elbv2_insecure_ssl_ciphers.py @@ -0,0 +1,39 @@ +from lib.check.models import Check, Check_Report +from providers.aws.services.elbv2.elbv2_client import elbv2_client + + +class elbv2_insecure_ssl_ciphers(Check): + def execute(self): + findings = [] + secure_ssl_policies = [ + "ELBSecurityPolicy-TLS-1-2-2017-01", + "ELBSecurityPolicy-TLS-1-2-Ext-2018-06", + "ELBSecurityPolicy-FS-1-2-2019-08", + "ELBSecurityPolicy-FS-1-2-Res-2019-08", + "ELBSecurityPolicy-FS-1-2-Res-2020-10", + "ELBSecurityPolicy-TLS13-1-2-2021-06", + "ELBSecurityPolicy-TLS13-1-3-2021-06", + "ELBSecurityPolicy-TLS13-1-2-Res-2021-06", + "ELBSecurityPolicy-TLS13-1-2-Ext1-2021-06", + "ELBSecurityPolicy-TLS13-1-2-Ext2-2021-06", + ] + for lb in elbv2_client.loadbalancersv2: + report = Check_Report(self.metadata) + report.region = lb.region + report.resource_id = lb.name + report.resource_arn = lb.arn + report.status = "PASS" + report.status_extended = ( + f"ELBv2 {lb.name} has not insecure SSL protocols or ciphers." + ) + for listener in lb.listeners: + if ( + listener.protocol == "HTTPS" + and listener.ssl_policy not in secure_ssl_policies + ): + report.status = "FAIL" + report.status_extended = f"ELBv2 {lb.name} has listeners with insecure SSL protocols or ciphers." + + findings.append(report) + + return findings diff --git a/providers/aws/services/elbv2/elbv2_insecure_ssl_ciphers/elbv2_insecure_ssl_ciphers_test.py b/providers/aws/services/elbv2/elbv2_insecure_ssl_ciphers/elbv2_insecure_ssl_ciphers_test.py new file mode 100644 index 00000000..30b90c3a --- /dev/null +++ b/providers/aws/services/elbv2/elbv2_insecure_ssl_ciphers/elbv2_insecure_ssl_ciphers_test.py @@ -0,0 +1,178 @@ +from re import search +from unittest import mock + +from boto3 import client, resource +from moto import mock_ec2, mock_elbv2 + +AWS_REGION = "eu-west-1" +AWS_ACCOUNT_NUMBER = "123456789012" + + +class Test_elbv2_insecure_ssl_ciphers: + @mock_elbv2 + def test_elb_no_balancers(self): + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elbv2.elbv2_service import ELBv2 + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elbv2.elbv2_insecure_ssl_ciphers.elbv2_insecure_ssl_ciphers.elbv2_client", + new=ELBv2(current_audit_info), + ): + # Test Check + from providers.aws.services.elbv2.elbv2_insecure_ssl_ciphers.elbv2_insecure_ssl_ciphers import ( + elbv2_insecure_ssl_ciphers, + ) + + check = elbv2_insecure_ssl_ciphers() + result = check.execute() + + assert len(result) == 0 + + @mock_ec2 + @mock_elbv2 + def test_elbv2_listener_with_secure_policy(self): + conn = client("elbv2", region_name=AWS_REGION) + ec2 = resource("ec2", region_name=AWS_REGION) + + security_group = ec2.create_security_group( + GroupName="a-security-group", Description="First One" + ) + vpc = ec2.create_vpc(CidrBlock="172.28.7.0/24", InstanceTenancy="default") + subnet1 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.192/26", AvailabilityZone=f"{AWS_REGION}a" + ) + subnet2 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.0/26", AvailabilityZone=f"{AWS_REGION}b" + ) + + lb = conn.create_load_balancer( + Name="my-lb", + Subnets=[subnet1.id, subnet2.id], + SecurityGroups=[security_group.id], + Scheme="internal", + Type="application", + )["LoadBalancers"][0] + + response = conn.create_target_group( + Name="a-target", + Protocol="HTTP", + Port=8080, + VpcId=vpc.id, + HealthCheckProtocol="HTTP", + HealthCheckPort="8080", + HealthCheckPath="/", + HealthCheckIntervalSeconds=5, + HealthCheckTimeoutSeconds=5, + HealthyThresholdCount=5, + UnhealthyThresholdCount=2, + Matcher={"HttpCode": "200"}, + ) + target_group = response.get("TargetGroups")[0] + target_group_arn = target_group["TargetGroupArn"] + response = conn.create_listener( + LoadBalancerArn=lb["LoadBalancerArn"], + Protocol="HTTPS", + Port=443, + SslPolicy="ELBSecurityPolicy-TLS-1-2-2017-01", + DefaultActions=[{"Type": "forward", "TargetGroupArn": target_group_arn}], + ) + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elbv2.elbv2_service import ELBv2 + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elbv2.elbv2_insecure_ssl_ciphers.elbv2_insecure_ssl_ciphers.elbv2_client", + new=ELBv2(current_audit_info), + ): + from providers.aws.services.elbv2.elbv2_insecure_ssl_ciphers.elbv2_insecure_ssl_ciphers import ( + elbv2_insecure_ssl_ciphers, + ) + + check = elbv2_insecure_ssl_ciphers() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert search( + "has not insecure SSL protocols or ciphers", + result[0].status_extended, + ) + assert result[0].resource_id == "my-lb" + assert result[0].resource_arn == lb["LoadBalancerArn"] + + @mock_ec2 + @mock_elbv2 + def test_elbv2_with_HTTPS_listener(self): + conn = client("elbv2", region_name=AWS_REGION) + ec2 = resource("ec2", region_name=AWS_REGION) + + security_group = ec2.create_security_group( + GroupName="a-security-group", Description="First One" + ) + vpc = ec2.create_vpc(CidrBlock="172.28.7.0/24", InstanceTenancy="default") + subnet1 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.192/26", AvailabilityZone=f"{AWS_REGION}a" + ) + subnet2 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.0/26", AvailabilityZone=f"{AWS_REGION}b" + ) + + lb = conn.create_load_balancer( + Name="my-lb", + Subnets=[subnet1.id, subnet2.id], + SecurityGroups=[security_group.id], + Scheme="internal", + )["LoadBalancers"][0] + + response = conn.create_target_group( + Name="a-target", + Protocol="HTTP", + Port=8080, + VpcId=vpc.id, + HealthCheckProtocol="HTTP", + HealthCheckPort="8080", + HealthCheckPath="/", + HealthCheckIntervalSeconds=5, + HealthCheckTimeoutSeconds=5, + HealthyThresholdCount=5, + UnhealthyThresholdCount=2, + Matcher={"HttpCode": "200"}, + ) + target_group = response.get("TargetGroups")[0] + target_group_arn = target_group["TargetGroupArn"] + response = conn.create_listener( + LoadBalancerArn=lb["LoadBalancerArn"], + Protocol="HTTPS", + SslPolicy="ELBSecurityPolicy-TLS-1-1-2017-01", + DefaultActions=[{"Type": "forward", "TargetGroupArn": target_group_arn}], + ) + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elbv2.elbv2_service import ELBv2 + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elbv2.elbv2_insecure_ssl_ciphers.elbv2_insecure_ssl_ciphers.elbv2_client", + new=ELBv2(current_audit_info), + ): + from providers.aws.services.elbv2.elbv2_insecure_ssl_ciphers.elbv2_insecure_ssl_ciphers import ( + elbv2_insecure_ssl_ciphers, + ) + + check = elbv2_insecure_ssl_ciphers() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert search( + "has listeners with insecure SSL protocols or ciphers", + result[0].status_extended, + ) + assert result[0].resource_id == "my-lb" + assert result[0].resource_arn == lb["LoadBalancerArn"] diff --git a/providers/aws/services/elbv2/elbv2_internet_facing/__init__.py b/providers/aws/services/elbv2/elbv2_internet_facing/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/providers/aws/services/elbv2/elbv2_internet_facing/elbv2_internet_facing.metadata.json b/providers/aws/services/elbv2/elbv2_internet_facing/elbv2_internet_facing.metadata.json new file mode 100644 index 00000000..22d67111 --- /dev/null +++ b/providers/aws/services/elbv2/elbv2_internet_facing/elbv2_internet_facing.metadata.json @@ -0,0 +1,35 @@ +{ + "Provider": "aws", + "CheckID": "elbv2_internet_facing", + "CheckTitle": "Check for internet facing Elastic Load Balancers.", + "CheckType": ["Data Protection"], + "ServiceName": "elbv2", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "Severity": "medium", + "ResourceType": "AwsElasticLoadBalancingV2LoadBalancer", + "Description": "Check for internet facing Elastic Load Balancers.", + "Risk": "Publicly accessible load balancers could expose sensitive data to bad actors.", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/ELBv2/internet-facing-load-balancers.html", + "Terraform": "" + }, + "Recommendation": { + "Text": "Ensure the load balancer should be publicly accessible. If publicly exposed ensure a WAF ACL is implemented.", + "Url": "https://docs.aws.amazon.com/waf/latest/developerguide/web-acl-associating-aws-resource.html" + } + }, + "Categories": [], + "Tags": { + "Tag1Key": "value", + "Tag2Key": "value" + }, + "DependsOn": [], + "RelatedTo": [], + "Notes": "", + "Compliance": [] + } diff --git a/providers/aws/services/elbv2/elbv2_internet_facing/elbv2_internet_facing.py b/providers/aws/services/elbv2/elbv2_internet_facing/elbv2_internet_facing.py new file mode 100644 index 00000000..1e0fc3a1 --- /dev/null +++ b/providers/aws/services/elbv2/elbv2_internet_facing/elbv2_internet_facing.py @@ -0,0 +1,23 @@ +from lib.check.models import Check, Check_Report +from providers.aws.services.elbv2.elbv2_client import elbv2_client + + +class elbv2_internet_facing(Check): + def execute(self): + findings = [] + for lb in elbv2_client.loadbalancersv2: + report = Check_Report(self.metadata) + report.region = lb.region + report.resource_id = lb.name + report.resource_arn = lb.arn + report.status = "PASS" + report.status_extended = f"ELBv2 ALB {lb.name} is not internet facing." + if lb.scheme == "internet-facing": + report.status = "FAIL" + report.status_extended = ( + f"ELBv2 ALB {lb.name} is internet facing in {lb.dns}." + ) + + findings.append(report) + + return findings diff --git a/providers/aws/services/elbv2/elbv2_internet_facing/elbv2_internet_facing_test.py b/providers/aws/services/elbv2/elbv2_internet_facing/elbv2_internet_facing_test.py new file mode 100644 index 00000000..b354b399 --- /dev/null +++ b/providers/aws/services/elbv2/elbv2_internet_facing/elbv2_internet_facing_test.py @@ -0,0 +1,131 @@ +from re import search +from unittest import mock + +from boto3 import client, resource +from moto import mock_ec2, mock_elbv2 + +AWS_REGION = "eu-west-1" +AWS_ACCOUNT_NUMBER = "123456789012" + + +class Test_elbv2_request_smugling: + @mock_elbv2 + def test_elb_no_balancers(self): + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elbv2.elbv2_service import ELBv2 + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elbv2.elbv2_internet_facing.elbv2_internet_facing.elbv2_client", + new=ELBv2(current_audit_info), + ): + # Test Check + from providers.aws.services.elbv2.elbv2_internet_facing.elbv2_internet_facing import ( + elbv2_internet_facing, + ) + + check = elbv2_internet_facing() + result = check.execute() + + assert len(result) == 0 + + @mock_ec2 + @mock_elbv2 + def test_elbv2_private(self): + conn = client("elbv2", region_name=AWS_REGION) + ec2 = resource("ec2", region_name=AWS_REGION) + + security_group = ec2.create_security_group( + GroupName="a-security-group", Description="First One" + ) + vpc = ec2.create_vpc(CidrBlock="172.28.7.0/24", InstanceTenancy="default") + subnet1 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.192/26", AvailabilityZone=f"{AWS_REGION}a" + ) + subnet2 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.0/26", AvailabilityZone=f"{AWS_REGION}b" + ) + + lb = conn.create_load_balancer( + Name="my-lb", + Subnets=[subnet1.id, subnet2.id], + SecurityGroups=[security_group.id], + Scheme="internal", + Type="application", + )["LoadBalancers"][0] + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elbv2.elbv2_service import ELBv2 + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elbv2.elbv2_internet_facing.elbv2_internet_facing.elbv2_client", + new=ELBv2(current_audit_info), + ): + from providers.aws.services.elbv2.elbv2_internet_facing.elbv2_internet_facing import ( + elbv2_internet_facing, + ) + + check = elbv2_internet_facing() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert search( + "is not internet facing", + result[0].status_extended, + ) + assert result[0].resource_id == "my-lb" + assert result[0].resource_arn == lb["LoadBalancerArn"] + + @mock_ec2 + @mock_elbv2 + def test_elbv2_with_deletion_protection(self): + conn = client("elbv2", region_name=AWS_REGION) + ec2 = resource("ec2", region_name=AWS_REGION) + + security_group = ec2.create_security_group( + GroupName="a-security-group", Description="First One" + ) + vpc = ec2.create_vpc(CidrBlock="172.28.7.0/24", InstanceTenancy="default") + subnet1 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.192/26", AvailabilityZone=f"{AWS_REGION}a" + ) + subnet2 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.0/26", AvailabilityZone=f"{AWS_REGION}b" + ) + + lb = conn.create_load_balancer( + Name="my-lb", + Subnets=[subnet1.id, subnet2.id], + SecurityGroups=[security_group.id], + Scheme="internet-facing", + )["LoadBalancers"][0] + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elbv2.elbv2_service import ELBv2 + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elbv2.elbv2_internet_facing.elbv2_internet_facing.elbv2_client", + new=ELBv2(current_audit_info), + ): + from providers.aws.services.elbv2.elbv2_internet_facing.elbv2_internet_facing import ( + elbv2_internet_facing, + ) + + check = elbv2_internet_facing() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert search( + "is internet facing", + result[0].status_extended, + ) + assert result[0].resource_id == "my-lb" + assert result[0].resource_arn == lb["LoadBalancerArn"] diff --git a/providers/aws/services/elbv2/elbv2_listeners_underneath/__init__.py b/providers/aws/services/elbv2/elbv2_listeners_underneath/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/providers/aws/services/elbv2/elbv2_listeners_underneath/elbv2_listeners_underneath.metadata.json b/providers/aws/services/elbv2/elbv2_listeners_underneath/elbv2_listeners_underneath.metadata.json new file mode 100644 index 00000000..8f870111 --- /dev/null +++ b/providers/aws/services/elbv2/elbv2_listeners_underneath/elbv2_listeners_underneath.metadata.json @@ -0,0 +1,35 @@ +{ + "Provider": "aws", + "CheckID": "elbv2_listeners_underneath", + "CheckTitle": "Check if ELBV2 has listeners underneath.", + "CheckType": ["Data Protection"], + "ServiceName": "elbv2", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "Severity": "medium", + "ResourceType": "AwsElasticLoadBalancingV2LoadBalancer", + "Description": "Check if ELBV2 has listeners underneath.", + "Risk": "The rules that are defined for a listener determine how the load balancer routes requests to its registered targets.", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "Add listeners to Elastic Load Balancers V2.", + "Url": "https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-listeners.html" + } + }, + "Categories": [], + "Tags": { + "Tag1Key": "value", + "Tag2Key": "value" + }, + "DependsOn": [], + "RelatedTo": [], + "Notes": "", + "Compliance": [] + } diff --git a/providers/aws/services/elbv2/elbv2_listeners_underneath/elbv2_listeners_underneath.py b/providers/aws/services/elbv2/elbv2_listeners_underneath/elbv2_listeners_underneath.py new file mode 100644 index 00000000..be59e687 --- /dev/null +++ b/providers/aws/services/elbv2/elbv2_listeners_underneath/elbv2_listeners_underneath.py @@ -0,0 +1,21 @@ +from lib.check.models import Check, Check_Report +from providers.aws.services.elbv2.elbv2_client import elbv2_client + + +class elbv2_listeners_underneath(Check): + def execute(self): + findings = [] + for lb in elbv2_client.loadbalancersv2: + report = Check_Report(self.metadata) + report.region = lb.region + report.resource_id = lb.name + report.resource_arn = lb.arn + report.status = "PASS" + report.status_extended = f"ELBv2 {lb.name} has listeners underneath." + if len(lb.listeners) == 0: + report.status = "FAIL" + report.status_extended = f"ELBv2 {lb.name} has no listeners underneath." + + findings.append(report) + + return findings diff --git a/providers/aws/services/elbv2/elbv2_listeners_underneath/elbv2_listeners_underneath_test.py b/providers/aws/services/elbv2/elbv2_listeners_underneath/elbv2_listeners_underneath_test.py new file mode 100644 index 00000000..572214fb --- /dev/null +++ b/providers/aws/services/elbv2/elbv2_listeners_underneath/elbv2_listeners_underneath_test.py @@ -0,0 +1,150 @@ +from re import search +from unittest import mock + +from boto3 import client, resource +from moto import mock_ec2, mock_elbv2 + +AWS_REGION = "eu-west-1" +AWS_ACCOUNT_NUMBER = "123456789012" + + +class Test_elbv2_listeners_underneath: + @mock_elbv2 + def test_elb_no_balancers(self): + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elbv2.elbv2_service import ELBv2 + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elbv2.elbv2_listeners_underneath.elbv2_listeners_underneath.elbv2_client", + new=ELBv2(current_audit_info), + ): + # Test Check + from providers.aws.services.elbv2.elbv2_listeners_underneath.elbv2_listeners_underneath import ( + elbv2_listeners_underneath, + ) + + check = elbv2_listeners_underneath() + result = check.execute() + + assert len(result) == 0 + + @mock_ec2 + @mock_elbv2 + def test_elbv2_without_listeners(self): + conn = client("elbv2", region_name=AWS_REGION) + ec2 = resource("ec2", region_name=AWS_REGION) + + security_group = ec2.create_security_group( + GroupName="a-security-group", Description="First One" + ) + vpc = ec2.create_vpc(CidrBlock="172.28.7.0/24", InstanceTenancy="default") + subnet1 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.192/26", AvailabilityZone=f"{AWS_REGION}a" + ) + subnet2 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.0/26", AvailabilityZone=f"{AWS_REGION}b" + ) + + lb = conn.create_load_balancer( + Name="my-lb", + Subnets=[subnet1.id, subnet2.id], + SecurityGroups=[security_group.id], + Scheme="internal", + Type="application", + )["LoadBalancers"][0] + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elbv2.elbv2_service import ELBv2 + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elbv2.elbv2_listeners_underneath.elbv2_listeners_underneath.elbv2_client", + new=ELBv2(current_audit_info), + ): + from providers.aws.services.elbv2.elbv2_listeners_underneath.elbv2_listeners_underneath import ( + elbv2_listeners_underneath, + ) + + check = elbv2_listeners_underneath() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert search( + "has no listeners underneath", + result[0].status_extended, + ) + assert result[0].resource_id == "my-lb" + assert result[0].resource_arn == lb["LoadBalancerArn"] + + @mock_ec2 + @mock_elbv2 + def test_elbv2_with_listeners(self): + conn = client("elbv2", region_name=AWS_REGION) + ec2 = resource("ec2", region_name=AWS_REGION) + + security_group = ec2.create_security_group( + GroupName="a-security-group", Description="First One" + ) + vpc = ec2.create_vpc(CidrBlock="172.28.7.0/24", InstanceTenancy="default") + subnet1 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.192/26", AvailabilityZone=f"{AWS_REGION}a" + ) + subnet2 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.0/26", AvailabilityZone=f"{AWS_REGION}b" + ) + + lb = conn.create_load_balancer( + Name="my-lb", + Subnets=[subnet1.id, subnet2.id], + SecurityGroups=[security_group.id], + Scheme="internal", + )["LoadBalancers"][0] + + response = conn.create_target_group( + Name="a-target", + Protocol="HTTP", + Port=8080, + VpcId=vpc.id, + HealthCheckProtocol="HTTP", + HealthCheckPort="8080", + HealthCheckPath="/", + HealthCheckIntervalSeconds=5, + HealthCheckTimeoutSeconds=5, + HealthyThresholdCount=5, + UnhealthyThresholdCount=2, + Matcher={"HttpCode": "200"}, + ) + target_group = response.get("TargetGroups")[0] + target_group_arn = target_group["TargetGroupArn"] + response = conn.create_listener( + LoadBalancerArn=lb["LoadBalancerArn"], + Protocol="HTTP", + DefaultActions=[{"Type": "forward", "TargetGroupArn": target_group_arn}], + ) + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elbv2.elbv2_service import ELBv2 + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elbv2.elbv2_listeners_underneath.elbv2_listeners_underneath.elbv2_client", + new=ELBv2(current_audit_info), + ): + from providers.aws.services.elbv2.elbv2_listeners_underneath.elbv2_listeners_underneath import ( + elbv2_listeners_underneath, + ) + + check = elbv2_listeners_underneath() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert search("has listeners underneath", result[0].status_extended) + assert result[0].resource_id == "my-lb" + assert result[0].resource_arn == lb["LoadBalancerArn"] diff --git a/providers/aws/services/elbv2/elbv2_logging_enabled/__init__.py b/providers/aws/services/elbv2/elbv2_logging_enabled/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/providers/aws/services/elbv2/elbv2_logging_enabled/elbv2_logging_enabled.metadata.json b/providers/aws/services/elbv2/elbv2_logging_enabled/elbv2_logging_enabled.metadata.json new file mode 100644 index 00000000..20ff6fcc --- /dev/null +++ b/providers/aws/services/elbv2/elbv2_logging_enabled/elbv2_logging_enabled.metadata.json @@ -0,0 +1,35 @@ +{ + "Provider": "aws", + "CheckID": "elbv2_logging_enabled", + "CheckTitle": "Check if Elastic Load Balancers have logging enabled.", + "CheckType": ["Logging and Monitoring"], + "ServiceName": "elbv2", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "Severity": "medium", + "ResourceType": "AwsElasticLoadBalancingV2LoadBalancer", + "Description": "Check if Elastic Load Balancers have logging enabled.", + "Risk": "If logs are not enabled monitoring of service use and threat analysis is not possible.", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "aws elbv2 modify-load-balancer-attributes --load-balancer-arn --attributes Key=access_logs.s3.enabled,Value=true Key=access_logs.s3.bucket,Value= Key=access_logs.s3.prefix,Value=", + "NativeIaC": "https://docs.bridgecrew.io/docs/bc_aws_logging_22#cloudformation", + "Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/ELBv2/access-log.html", + "Terraform": "https://docs.bridgecrew.io/docs/bc_aws_logging_22#terraform" + }, + "Recommendation": { + "Text": "Enable ELB logging, create la log lifecycle and define use cases.", + "Url": "https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-access-logs.html" + } + }, + "Categories": [], + "Tags": { + "Tag1Key": "value", + "Tag2Key": "value" + }, + "DependsOn": [], + "RelatedTo": [], + "Notes": "", + "Compliance": [] + } diff --git a/providers/aws/services/elbv2/elbv2_logging_enabled/elbv2_logging_enabled.py b/providers/aws/services/elbv2/elbv2_logging_enabled/elbv2_logging_enabled.py new file mode 100644 index 00000000..634e62f7 --- /dev/null +++ b/providers/aws/services/elbv2/elbv2_logging_enabled/elbv2_logging_enabled.py @@ -0,0 +1,25 @@ +from lib.check.models import Check, Check_Report +from providers.aws.services.elbv2.elbv2_client import elbv2_client + + +class elbv2_logging_enabled(Check): + def execute(self): + findings = [] + for lb in elbv2_client.loadbalancersv2: + report = Check_Report(self.metadata) + report.region = lb.region + report.resource_id = lb.name + report.resource_arn = lb.arn + report.status = "FAIL" + report.status_extended = ( + f"ELBv2 ALB {lb.name} has not configured access logs." + ) + if lb.access_logs == "true": + report.status = "PASS" + report.status_extended = ( + f"ELBv2 ALB {lb.name} has access logs to S3 configured." + ) + + findings.append(report) + + return findings diff --git a/providers/aws/services/elbv2/elbv2_logging_enabled/elbv2_logging_enabled_test.py b/providers/aws/services/elbv2/elbv2_logging_enabled/elbv2_logging_enabled_test.py new file mode 100644 index 00000000..bb5ae984 --- /dev/null +++ b/providers/aws/services/elbv2/elbv2_logging_enabled/elbv2_logging_enabled_test.py @@ -0,0 +1,151 @@ +from re import search +from unittest import mock + +from boto3 import client, resource +from moto import mock_ec2, mock_elbv2 + +AWS_REGION = "eu-west-1" +AWS_ACCOUNT_NUMBER = "123456789012" + + +class Test_elbv2_logging_enabled: + @mock_elbv2 + def test_elb_no_balancers(self): + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elbv2.elbv2_service import ELBv2 + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elbv2.elbv2_logging_enabled.elbv2_logging_enabled.elbv2_client", + new=ELBv2(current_audit_info), + ): + # Test Check + from providers.aws.services.elbv2.elbv2_logging_enabled.elbv2_logging_enabled import ( + elbv2_logging_enabled, + ) + + check = elbv2_logging_enabled() + result = check.execute() + + assert len(result) == 0 + + @mock_ec2 + @mock_elbv2 + def test_elbv2_without_deletion_protection(self): + conn = client("elbv2", region_name=AWS_REGION) + ec2 = resource("ec2", region_name=AWS_REGION) + + security_group = ec2.create_security_group( + GroupName="a-security-group", Description="First One" + ) + vpc = ec2.create_vpc(CidrBlock="172.28.7.0/24", InstanceTenancy="default") + subnet1 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.192/26", AvailabilityZone=f"{AWS_REGION}a" + ) + subnet2 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.0/26", AvailabilityZone=f"{AWS_REGION}b" + ) + + lb = conn.create_load_balancer( + Name="my-lb", + Subnets=[subnet1.id, subnet2.id], + SecurityGroups=[security_group.id], + Scheme="internal", + Type="application", + )["LoadBalancers"][0] + + conn.modify_load_balancer_attributes( + LoadBalancerArn=lb["LoadBalancerArn"], + Attributes=[ + { + "Key": "access_logs.s3.enabled", + "Value": "false", + }, + ], + ) + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elbv2.elbv2_service import ELBv2 + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elbv2.elbv2_logging_enabled.elbv2_logging_enabled.elbv2_client", + new=ELBv2(current_audit_info), + ): + from providers.aws.services.elbv2.elbv2_logging_enabled.elbv2_logging_enabled import ( + elbv2_logging_enabled, + ) + + check = elbv2_logging_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert search( + "has not configured access logs", + result[0].status_extended, + ) + assert result[0].resource_id == "my-lb" + assert result[0].resource_arn == lb["LoadBalancerArn"] + + @mock_ec2 + @mock_elbv2 + def test_elbv2_with_deletion_protection(self): + conn = client("elbv2", region_name=AWS_REGION) + ec2 = resource("ec2", region_name=AWS_REGION) + + security_group = ec2.create_security_group( + GroupName="a-security-group", Description="First One" + ) + vpc = ec2.create_vpc(CidrBlock="172.28.7.0/24", InstanceTenancy="default") + subnet1 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.192/26", AvailabilityZone=f"{AWS_REGION}a" + ) + subnet2 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.0/26", AvailabilityZone=f"{AWS_REGION}b" + ) + + lb = conn.create_load_balancer( + Name="my-lb", + Subnets=[subnet1.id, subnet2.id], + SecurityGroups=[security_group.id], + Scheme="internal", + )["LoadBalancers"][0] + + conn.modify_load_balancer_attributes( + LoadBalancerArn=lb["LoadBalancerArn"], + Attributes=[ + { + "Key": "access_logs.s3.enabled", + "Value": "true", + }, + ], + ) + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elbv2.elbv2_service import ELBv2 + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elbv2.elbv2_logging_enabled.elbv2_logging_enabled.elbv2_client", + new=ELBv2(current_audit_info), + ): + from providers.aws.services.elbv2.elbv2_logging_enabled.elbv2_logging_enabled import ( + elbv2_logging_enabled, + ) + + check = elbv2_logging_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert search( + "has access logs to S3 configured", + result[0].status_extended, + ) + assert result[0].resource_id == "my-lb" + assert result[0].resource_arn == lb["LoadBalancerArn"] diff --git a/providers/aws/services/elbv2/elbv2_request_smugling/__init__.py b/providers/aws/services/elbv2/elbv2_request_smugling/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/providers/aws/services/elbv2/elbv2_request_smugling/elbv2_request_smugling.metadata.json b/providers/aws/services/elbv2/elbv2_request_smugling/elbv2_request_smugling.metadata.json new file mode 100644 index 00000000..f9ee18c2 --- /dev/null +++ b/providers/aws/services/elbv2/elbv2_request_smugling/elbv2_request_smugling.metadata.json @@ -0,0 +1,35 @@ +{ + "Provider": "aws", + "CheckID": "elbv2_request_smugling", + "CheckTitle": "Check if Application Load Balancer is dropping invalid packets to prevent header based HTTP request smuggling.", + "CheckType": ["Data Protection"], + "ServiceName": "elbv2", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "Severity": "medium", + "ResourceType": "AwsElasticLoadBalancingV2LoadBalancer", + "Description": "Check if Application Load Balancer is dropping invalid packets to prevent header based HTTP request smuggling.", + "Risk": "ALB can be target of actors sending bad HTTP headers.", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "aws elbv2 modify-load-balancer-attributes --load-balancer-arn --attributes Key=routing.http.drop_invalid_header_fields.enabled,Value=true", + "NativeIaC": "", + "Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/ELBv2/drop-invalid-header-fields-enabled.html", + "Terraform": "https://docs.bridgecrew.io/docs/ensure-that-alb-drops-http-headers#terraform" + }, + "Recommendation": { + "Text": "Ensure Application Load Balancer is configured for HTTP headers with header fields that are not valid are removed by the load balancer (true).", + "Url": "https://docs.aws.amazon.com/elasticloadbalancing/latest/application/application-load-balancers.html#deletion-protection" + } + }, + "Categories": [], + "Tags": { + "Tag1Key": "value", + "Tag2Key": "value" + }, + "DependsOn": [], + "RelatedTo": [], + "Notes": "", + "Compliance": [] + } diff --git a/providers/aws/services/elbv2/elbv2_request_smugling/elbv2_request_smugling.py b/providers/aws/services/elbv2/elbv2_request_smugling/elbv2_request_smugling.py new file mode 100644 index 00000000..a079e9ca --- /dev/null +++ b/providers/aws/services/elbv2/elbv2_request_smugling/elbv2_request_smugling.py @@ -0,0 +1,26 @@ +from lib.check.models import Check, Check_Report +from providers.aws.services.elbv2.elbv2_client import elbv2_client + + +class elbv2_request_smugling(Check): + def execute(self): + findings = [] + for lb in elbv2_client.loadbalancersv2: + if lb.type == "application": + report = Check_Report(self.metadata) + report.region = lb.region + report.resource_id = lb.name + report.resource_arn = lb.arn + report.status = "FAIL" + report.status_extended = ( + f"ELBv2 ALB {lb.name} is not dropping invalid header fields." + ) + if lb.drop_invalid_header_fields == "true": + report.status = "PASS" + report.status_extended = ( + f"ELBv2 ALB {lb.name} is dropping invalid header fields." + ) + + findings.append(report) + + return findings diff --git a/providers/aws/services/elbv2/elbv2_request_smugling/elbv2_request_smugling_test.py b/providers/aws/services/elbv2/elbv2_request_smugling/elbv2_request_smugling_test.py new file mode 100644 index 00000000..08b98df9 --- /dev/null +++ b/providers/aws/services/elbv2/elbv2_request_smugling/elbv2_request_smugling_test.py @@ -0,0 +1,151 @@ +from re import search +from unittest import mock + +from boto3 import client, resource +from moto import mock_ec2, mock_elbv2 + +AWS_REGION = "eu-west-1" +AWS_ACCOUNT_NUMBER = "123456789012" + + +class Test_elbv2_request_smugling: + @mock_elbv2 + def test_elb_no_balancers(self): + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elbv2.elbv2_service import ELBv2 + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elbv2.elbv2_request_smugling.elbv2_request_smugling.elbv2_client", + new=ELBv2(current_audit_info), + ): + # Test Check + from providers.aws.services.elbv2.elbv2_request_smugling.elbv2_request_smugling import ( + elbv2_request_smugling, + ) + + check = elbv2_request_smugling() + result = check.execute() + + assert len(result) == 0 + + @mock_ec2 + @mock_elbv2 + def test_elbv2_without_dropping(self): + conn = client("elbv2", region_name=AWS_REGION) + ec2 = resource("ec2", region_name=AWS_REGION) + + security_group = ec2.create_security_group( + GroupName="a-security-group", Description="First One" + ) + vpc = ec2.create_vpc(CidrBlock="172.28.7.0/24", InstanceTenancy="default") + subnet1 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.192/26", AvailabilityZone=f"{AWS_REGION}a" + ) + subnet2 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.0/26", AvailabilityZone=f"{AWS_REGION}b" + ) + + lb = conn.create_load_balancer( + Name="my-lb", + Subnets=[subnet1.id, subnet2.id], + SecurityGroups=[security_group.id], + Scheme="internal", + Type="application", + )["LoadBalancers"][0] + + conn.modify_load_balancer_attributes( + LoadBalancerArn=lb["LoadBalancerArn"], + Attributes=[ + { + "Key": "routing.http.drop_invalid_header_fields.enabled", + "Value": "false", + }, + ], + ) + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elbv2.elbv2_service import ELBv2 + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elbv2.elbv2_request_smugling.elbv2_request_smugling.elbv2_client", + new=ELBv2(current_audit_info), + ): + from providers.aws.services.elbv2.elbv2_request_smugling.elbv2_request_smugling import ( + elbv2_request_smugling, + ) + + check = elbv2_request_smugling() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert search( + "is not dropping invalid header fields", + result[0].status_extended, + ) + assert result[0].resource_id == "my-lb" + assert result[0].resource_arn == lb["LoadBalancerArn"] + + @mock_ec2 + @mock_elbv2 + def test_elbv2_with_dropping(self): + conn = client("elbv2", region_name=AWS_REGION) + ec2 = resource("ec2", region_name=AWS_REGION) + + security_group = ec2.create_security_group( + GroupName="a-security-group", Description="First One" + ) + vpc = ec2.create_vpc(CidrBlock="172.28.7.0/24", InstanceTenancy="default") + subnet1 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.192/26", AvailabilityZone=f"{AWS_REGION}a" + ) + subnet2 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.0/26", AvailabilityZone=f"{AWS_REGION}b" + ) + + lb = conn.create_load_balancer( + Name="my-lb", + Subnets=[subnet1.id, subnet2.id], + SecurityGroups=[security_group.id], + Scheme="internal", + )["LoadBalancers"][0] + + conn.modify_load_balancer_attributes( + LoadBalancerArn=lb["LoadBalancerArn"], + Attributes=[ + { + "Key": "routing.http.drop_invalid_header_fields.enabled", + "Value": "true", + }, + ], + ) + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elbv2.elbv2_service import ELBv2 + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elbv2.elbv2_request_smugling.elbv2_request_smugling.elbv2_client", + new=ELBv2(current_audit_info), + ): + from providers.aws.services.elbv2.elbv2_request_smugling.elbv2_request_smugling import ( + elbv2_request_smugling, + ) + + check = elbv2_request_smugling() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert search( + "is dropping invalid header fields", + result[0].status_extended, + ) + assert result[0].resource_id == "my-lb" + assert result[0].resource_arn == lb["LoadBalancerArn"] diff --git a/providers/aws/services/elbv2/elbv2_service.py b/providers/aws/services/elbv2/elbv2_service.py new file mode 100644 index 00000000..ddb01d87 --- /dev/null +++ b/providers/aws/services/elbv2/elbv2_service.py @@ -0,0 +1,162 @@ +import threading +from typing import Optional + +from pydantic import BaseModel + +from lib.logger import logger +from providers.aws.aws_provider import generate_regional_clients + + +################### ELBv2 +class ELBv2: + def __init__(self, audit_info): + self.service = "elbv2" + self.session = audit_info.audit_session + self.regional_clients = generate_regional_clients(self.service, audit_info) + self.loadbalancersv2 = [] + self.__threading_call__(self.__describe_load_balancers__) + self.listeners = [] + self.__threading_call__(self.__describe_listeners__) + self.__threading_call__(self.__describe_load_balancer_attributes__) + self.__threading_call__(self.__describe_rules__) + + def __get_session__(self): + return self.session + + def __threading_call__(self, call): + threads = [] + for regional_client in self.regional_clients.values(): + threads.append(threading.Thread(target=call, args=(regional_client,))) + for t in threads: + t.start() + for t in threads: + t.join() + + def __describe_load_balancers__(self, regional_client): + logger.info("ELBv2 - Describing load balancers...") + try: + describe_elbv2_paginator = regional_client.get_paginator( + "describe_load_balancers" + ) + for page in describe_elbv2_paginator.paginate(): + for elbv2 in page["LoadBalancers"]: + self.loadbalancersv2.append( + LoadBalancerv2( + name=elbv2["LoadBalancerName"], + dns=elbv2["DNSName"], + region=regional_client.region, + arn=elbv2["LoadBalancerArn"], + scheme=elbv2["Scheme"], + type=elbv2["Type"], + listeners=[], + ) + ) + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + def __describe_listeners__(self, regional_client): + logger.info("ELBv2 - Describing listeners...") + try: + for lb in self.loadbalancersv2: + if lb.region == regional_client.region: + describe_elbv2_paginator = regional_client.get_paginator( + "describe_listeners" + ) + for page in describe_elbv2_paginator.paginate( + LoadBalancerArn=lb.arn + ): + for listener in page["Listeners"]: + port = 0 + if "Port" in listener: + port = listener["Port"] + lb.listeners.append( + Listenerv2( + region=regional_client.region, + arn=listener["ListenerArn"], + port=port, + protocol=listener["Protocol"], + ssl_policy=listener.get("SslPolicy"), + rules=[], + ) + ) + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + def __describe_load_balancer_attributes__(self, regional_client): + logger.info("ELBv2 - Describing attributes...") + try: + for lb in self.loadbalancersv2: + if lb.region == regional_client.region: + for attribute in regional_client.describe_load_balancer_attributes( + LoadBalancerArn=lb.arn + )["Attributes"]: + if attribute["Key"] == "routing.http.desync_mitigation_mode": + lb.desync_mitigation_mode = attribute["Value"] + if attribute["Key"] == "deletion_protection.enabled": + lb.deletion_protection = attribute["Value"] + if attribute["Key"] == "access_logs.s3.enabled": + lb.access_logs = attribute["Value"] + if ( + attribute["Key"] + == "routing.http.drop_invalid_header_fields.enabled" + ): + lb.drop_invalid_header_fields = attribute["Value"] + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + def __describe_rules__(self, regional_client): + logger.info("ELBv2 - Describing Rules...") + try: + for lb in self.loadbalancersv2: + if lb.region == regional_client.region: + for listener in lb.listeners: + for rule in regional_client.describe_rules( + ListenerArn=listener.arn + )["Rules"]: + listener.rules.append( + ListenerRule( + arn=rule["RuleArn"], + actions=rule["Actions"], + conditions=rule["Conditions"], + ) + ) + + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + +class ListenerRule(BaseModel): + arn: str + actions: list[dict] + conditions: list[dict] + + +class Listenerv2(BaseModel): + arn: str + region: str + port: int + protocol: str + ssl_policy: Optional[str] + rules: list[ListenerRule] + + +class LoadBalancerv2(BaseModel): + name: str + dns: str + arn: str + region: str + scheme: str + type: str + access_logs: Optional[str] + desync_mitigation_mode: Optional[str] + deletion_protection: Optional[str] + drop_invalid_header_fields: Optional[str] + listeners: list[Listenerv2] diff --git a/providers/aws/services/elbv2/elbv2_service_test.py b/providers/aws/services/elbv2/elbv2_service_test.py new file mode 100644 index 00000000..22856a0f --- /dev/null +++ b/providers/aws/services/elbv2/elbv2_service_test.py @@ -0,0 +1,228 @@ +from boto3 import client, resource, session +from moto import mock_ec2, mock_elbv2 + +from providers.aws.lib.audit_info.models import AWS_Audit_Info +from providers.aws.services.elbv2.elbv2_service import ELBv2 + +AWS_ACCOUNT_NUMBER = 123456789012 +AWS_REGION = "us-east-1" + + +class Test_ELBv2_Service: + # Mocked Audit Info + def set_mocked_audit_info(self): + audit_info = AWS_Audit_Info( + 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=None, + organizations_metadata=None, + ) + return audit_info + + # Test ELBv2 Service + @mock_elbv2 + def test_service(self): + # ELBv2 client for this test class + audit_info = self.set_mocked_audit_info() + elbv2 = ELBv2(audit_info) + assert elbv2.service == "elbv2" + + # Test ELBv2 Client + @mock_elbv2 + def test_client(self): + # ELBv2 client for this test class + audit_info = self.set_mocked_audit_info() + elbv2 = ELBv2(audit_info) + for regional_client in elbv2.regional_clients.values(): + assert regional_client.__class__.__name__ == "ElasticLoadBalancingv2" + + # Test ELBv2 Session + @mock_elbv2 + def test__get_session__(self): + # ELBv2 client for this test class + audit_info = self.set_mocked_audit_info() + elbv2 = ELBv2(audit_info) + assert elbv2.session.__class__.__name__ == "Session" + + # Test ELBv2 Describe Load Balancers + @mock_ec2 + @mock_elbv2 + def test__describe_load_balancers__(self): + conn = client("elbv2", region_name=AWS_REGION) + ec2 = resource("ec2", region_name=AWS_REGION) + + security_group = ec2.create_security_group( + GroupName="a-security-group", Description="First One" + ) + vpc = ec2.create_vpc(CidrBlock="172.28.7.0/24", InstanceTenancy="default") + subnet1 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.192/26", AvailabilityZone=f"{AWS_REGION}a" + ) + subnet2 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.0/26", AvailabilityZone=f"{AWS_REGION}b" + ) + + lb = conn.create_load_balancer( + Name="my-lb", + Subnets=[subnet1.id, subnet2.id], + SecurityGroups=[security_group.id], + Scheme="internal", + )["LoadBalancers"][0] + # ELBv2 client for this test class + audit_info = self.set_mocked_audit_info() + elbv2 = ELBv2(audit_info) + assert len(elbv2.loadbalancersv2) == 1 + assert elbv2.loadbalancersv2[0].name == "my-lb" + assert elbv2.loadbalancersv2[0].region == AWS_REGION + assert elbv2.loadbalancersv2[0].scheme == "internal" + assert elbv2.loadbalancersv2[0].arn == lb["LoadBalancerArn"] + + # Test ELBv2 Describe Listeners + @mock_ec2 + @mock_elbv2 + def test__describe_listeners__(self): + conn = client("elbv2", region_name=AWS_REGION) + ec2 = resource("ec2", region_name=AWS_REGION) + + security_group = ec2.create_security_group( + GroupName="a-security-group", Description="First One" + ) + vpc = ec2.create_vpc(CidrBlock="172.28.7.0/24", InstanceTenancy="default") + subnet1 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.192/26", AvailabilityZone=f"{AWS_REGION}a" + ) + subnet2 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.0/26", AvailabilityZone=f"{AWS_REGION}b" + ) + + lb = conn.create_load_balancer( + Name="my-lb", + Subnets=[subnet1.id, subnet2.id], + SecurityGroups=[security_group.id], + Scheme="internal", + )["LoadBalancers"][0] + + conn.create_listener( + LoadBalancerArn=lb["LoadBalancerArn"], + Protocol="HTTP", + Port=443, + DefaultActions=[ + { + "Type": "redirect", + "RedirectConfig": { + "Protocol": "HTTPS", + "Port": "443", + "StatusCode": "HTTP_301", + }, + } + ], + ) + # ELBv2 client for this test class + audit_info = self.set_mocked_audit_info() + elbv2 = ELBv2(audit_info) + assert len(elbv2.loadbalancersv2[0].listeners) == 1 + assert elbv2.loadbalancersv2[0].listeners[0].protocol == "HTTP" + assert elbv2.loadbalancersv2[0].listeners[0].port == 443 + + # Test ELBv2 Describe Load Balancers Attributes + @mock_ec2 + @mock_elbv2 + def test__describe_load_balancer_attributes__(self): + conn = client("elbv2", region_name=AWS_REGION) + ec2 = resource("ec2", region_name=AWS_REGION) + + security_group = ec2.create_security_group( + GroupName="a-security-group", Description="First One" + ) + vpc = ec2.create_vpc(CidrBlock="172.28.7.0/24", InstanceTenancy="default") + subnet1 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.192/26", AvailabilityZone=f"{AWS_REGION}a" + ) + subnet2 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.0/26", AvailabilityZone=f"{AWS_REGION}b" + ) + + lb = conn.create_load_balancer( + Name="my-lb", + Subnets=[subnet1.id, subnet2.id], + SecurityGroups=[security_group.id], + Scheme="internal", + )["LoadBalancers"][0] + + conn.modify_load_balancer_attributes( + LoadBalancerArn=lb["LoadBalancerArn"], + Attributes=[ + {"Key": "routing.http.desync_mitigation_mode", "Value": "defensive"}, + {"Key": "access_logs.s3.enabled", "Value": "true"}, + {"Key": "deletion_protection.enabled", "Value": "true"}, + { + "Key": "routing.http.drop_invalid_header_fields.enabled", + "Value": "false", + }, + ], + ) + # ELBv2 client for this test class + audit_info = self.set_mocked_audit_info() + elbv2 = ELBv2(audit_info) + assert len(elbv2.loadbalancersv2) == 1 + assert elbv2.loadbalancersv2[0].desync_mitigation_mode == "defensive" + assert elbv2.loadbalancersv2[0].access_logs == "true" + assert elbv2.loadbalancersv2[0].deletion_protection == "true" + assert elbv2.loadbalancersv2[0].drop_invalid_header_fields == "false" + + # Test ELBv2 Describe Load Balancers Attributes + @mock_ec2 + @mock_elbv2 + def test__describe_rules__(self): + conn = client("elbv2", region_name=AWS_REGION) + ec2 = resource("ec2", region_name=AWS_REGION) + + security_group = ec2.create_security_group( + GroupName="a-security-group", Description="First One" + ) + vpc = ec2.create_vpc(CidrBlock="172.28.7.0/24", InstanceTenancy="default") + subnet1 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.192/26", AvailabilityZone=f"{AWS_REGION}a" + ) + subnet2 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.0/26", AvailabilityZone=f"{AWS_REGION}b" + ) + + lb = conn.create_load_balancer( + Name="my-lb", + Subnets=[subnet1.id, subnet2.id], + SecurityGroups=[security_group.id], + Scheme="internal", + )["LoadBalancers"][0] + + actions = [ + { + "Type": "redirect", + "RedirectConfig": { + "Protocol": "HTTPS", + "Port": "443", + "StatusCode": "HTTP_301", + }, + } + ] + conn.create_listener( + LoadBalancerArn=lb["LoadBalancerArn"], + Protocol="HTTP", + DefaultActions=actions, + ) + # ELBv2 client for this test class + audit_info = self.set_mocked_audit_info() + elbv2 = ELBv2(audit_info) + assert len(elbv2.loadbalancersv2) == 1 + assert elbv2.loadbalancersv2[0].listeners[0].rules[0].actions == actions diff --git a/providers/aws/services/elbv2/elbv2_ssl_listeners/__init__.py b/providers/aws/services/elbv2/elbv2_ssl_listeners/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/providers/aws/services/elbv2/elbv2_ssl_listeners/elbv2_ssl_listeners.metadata.json b/providers/aws/services/elbv2/elbv2_ssl_listeners/elbv2_ssl_listeners.metadata.json new file mode 100644 index 00000000..6afcbd7b --- /dev/null +++ b/providers/aws/services/elbv2/elbv2_ssl_listeners/elbv2_ssl_listeners.metadata.json @@ -0,0 +1,35 @@ +{ + "Provider": "aws", + "CheckID": "elbv2_ssl_listeners", + "CheckTitle": "Check if Elastic Load Balancers have SSL listeners.", + "CheckType": ["Data Protection"], + "ServiceName": "elbv2", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "Severity": "medium", + "ResourceType": "AwsElasticLoadBalancingV2LoadBalancer", + "Description": "Check if Elastic Load Balancers have SSL listeners.", + "Risk": "Clear text communication could affect privacy of information in transit.", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "aws elbv2 create-listener --load-balancer-arn --protocol HTTPS --port 443 --ssl-policy --certificates CertificateArn=,IsDefault=true", + "NativeIaC": "", + "Other": "https://docs.bridgecrew.io/docs/networking_36#aws-ec2-console", + "Terraform": "" + }, + "Recommendation": { + "Text": "Scan for Load Balancers with HTTP or TCP listeners and understand the reason for each of them. Check if the listener can be implemented as TLS instead.", + "Url": "https://docs.aws.amazon.com/elasticloadbalancing/latest/application/create-https-listener.html" + } + }, + "Categories": [], + "Tags": { + "Tag1Key": "value", + "Tag2Key": "value" + }, + "DependsOn": [], + "RelatedTo": [], + "Notes": "", + "Compliance": [] + } diff --git a/providers/aws/services/elbv2/elbv2_ssl_listeners/elbv2_ssl_listeners.py b/providers/aws/services/elbv2/elbv2_ssl_listeners/elbv2_ssl_listeners.py new file mode 100644 index 00000000..560ddc28 --- /dev/null +++ b/providers/aws/services/elbv2/elbv2_ssl_listeners/elbv2_ssl_listeners.py @@ -0,0 +1,36 @@ +from lib.check.models import Check, Check_Report +from providers.aws.services.elbv2.elbv2_client import elbv2_client + + +class elbv2_ssl_listeners(Check): + def execute(self): + findings = [] + for lb in elbv2_client.loadbalancersv2: + if lb.type == "application": + report = Check_Report(self.metadata) + report.region = lb.region + report.resource_id = lb.name + report.resource_arn = lb.arn + report.status = "PASS" + report.status_extended = ( + f"ELBv2 ALB {lb.name} has HTTPS listeners only." + ) + for listener in lb.listeners: + if listener.protocol == "HTTP": + report.status = "FAIL" + report.status_extended = ( + f"ELBv2 ALB {lb.name} has non-encrypted listeners." + ) + # Check if it redirects HTTP to HTTPS + for rule in listener.rules: + for action in rule.actions: + if ( + action["Type"] == "redirect" + and action["RedirectConfig"]["Protocol"] == "HTTPS" + ): + report.status = "PASS" + report.status_extended = f"ELBv2 ALB {lb.name} has HTTP listener but it redirects to HTTPS." + + findings.append(report) + + return findings diff --git a/providers/aws/services/elbv2/elbv2_ssl_listeners/elbv2_ssl_listeners_test.py b/providers/aws/services/elbv2/elbv2_ssl_listeners/elbv2_ssl_listeners_test.py new file mode 100644 index 00000000..dbc2f973 --- /dev/null +++ b/providers/aws/services/elbv2/elbv2_ssl_listeners/elbv2_ssl_listeners_test.py @@ -0,0 +1,239 @@ +from re import search +from unittest import mock + +from boto3 import client, resource +from moto import mock_ec2, mock_elbv2 + +AWS_REGION = "eu-west-1" +AWS_ACCOUNT_NUMBER = "123456789012" + + +class Test_elbv2_ssl_listeners: + @mock_elbv2 + def test_elb_no_balancers(self): + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elbv2.elbv2_service import ELBv2 + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elbv2.elbv2_ssl_listeners.elbv2_ssl_listeners.elbv2_client", + new=ELBv2(current_audit_info), + ): + # Test Check + from providers.aws.services.elbv2.elbv2_ssl_listeners.elbv2_ssl_listeners import ( + elbv2_ssl_listeners, + ) + + check = elbv2_ssl_listeners() + result = check.execute() + + assert len(result) == 0 + + @mock_ec2 + @mock_elbv2 + def test_elbv2_with_HTTP_listener(self): + conn = client("elbv2", region_name=AWS_REGION) + ec2 = resource("ec2", region_name=AWS_REGION) + + security_group = ec2.create_security_group( + GroupName="a-security-group", Description="First One" + ) + vpc = ec2.create_vpc(CidrBlock="172.28.7.0/24", InstanceTenancy="default") + subnet1 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.192/26", AvailabilityZone=f"{AWS_REGION}a" + ) + subnet2 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.0/26", AvailabilityZone=f"{AWS_REGION}b" + ) + + lb = conn.create_load_balancer( + Name="my-lb", + Subnets=[subnet1.id, subnet2.id], + SecurityGroups=[security_group.id], + Scheme="internal", + Type="application", + )["LoadBalancers"][0] + + response = conn.create_target_group( + Name="a-target", + Protocol="HTTP", + Port=8080, + VpcId=vpc.id, + HealthCheckProtocol="HTTP", + HealthCheckPort="8080", + HealthCheckPath="/", + HealthCheckIntervalSeconds=5, + HealthCheckTimeoutSeconds=5, + HealthyThresholdCount=5, + UnhealthyThresholdCount=2, + Matcher={"HttpCode": "200"}, + ) + target_group = response.get("TargetGroups")[0] + target_group_arn = target_group["TargetGroupArn"] + response = conn.create_listener( + LoadBalancerArn=lb["LoadBalancerArn"], + Protocol="HTTP", + DefaultActions=[{"Type": "forward", "TargetGroupArn": target_group_arn}], + ) + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elbv2.elbv2_service import ELBv2 + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elbv2.elbv2_ssl_listeners.elbv2_ssl_listeners.elbv2_client", + new=ELBv2(current_audit_info), + ): + from providers.aws.services.elbv2.elbv2_ssl_listeners.elbv2_ssl_listeners import ( + elbv2_ssl_listeners, + ) + + check = elbv2_ssl_listeners() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert search( + "has non-encrypted listeners", + result[0].status_extended, + ) + assert result[0].resource_id == "my-lb" + assert result[0].resource_arn == lb["LoadBalancerArn"] + + @mock_ec2 + @mock_elbv2 + def test_elbv2_with_HTTPS_listener(self): + conn = client("elbv2", region_name=AWS_REGION) + ec2 = resource("ec2", region_name=AWS_REGION) + + security_group = ec2.create_security_group( + GroupName="a-security-group", Description="First One" + ) + vpc = ec2.create_vpc(CidrBlock="172.28.7.0/24", InstanceTenancy="default") + subnet1 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.192/26", AvailabilityZone=f"{AWS_REGION}a" + ) + subnet2 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.0/26", AvailabilityZone=f"{AWS_REGION}b" + ) + + lb = conn.create_load_balancer( + Name="my-lb", + Subnets=[subnet1.id, subnet2.id], + SecurityGroups=[security_group.id], + Scheme="internal", + )["LoadBalancers"][0] + + response = conn.create_target_group( + Name="a-target", + Protocol="HTTP", + Port=8080, + VpcId=vpc.id, + HealthCheckProtocol="HTTP", + HealthCheckPort="8080", + HealthCheckPath="/", + HealthCheckIntervalSeconds=5, + HealthCheckTimeoutSeconds=5, + HealthyThresholdCount=5, + UnhealthyThresholdCount=2, + Matcher={"HttpCode": "200"}, + ) + target_group = response.get("TargetGroups")[0] + target_group_arn = target_group["TargetGroupArn"] + response = conn.create_listener( + LoadBalancerArn=lb["LoadBalancerArn"], + Protocol="HTTPS", + DefaultActions=[{"Type": "forward", "TargetGroupArn": target_group_arn}], + ) + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elbv2.elbv2_service import ELBv2 + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elbv2.elbv2_ssl_listeners.elbv2_ssl_listeners.elbv2_client", + new=ELBv2(current_audit_info), + ): + from providers.aws.services.elbv2.elbv2_ssl_listeners.elbv2_ssl_listeners import ( + elbv2_ssl_listeners, + ) + + check = elbv2_ssl_listeners() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert search( + "has HTTPS listeners only", + result[0].status_extended, + ) + assert result[0].resource_id == "my-lb" + assert result[0].resource_arn == lb["LoadBalancerArn"] + + @mock_ec2 + @mock_elbv2 + def test_elbv2_with_HTTPS_redirection(self): + conn = client("elbv2", region_name=AWS_REGION) + ec2 = resource("ec2", region_name=AWS_REGION) + + security_group = ec2.create_security_group( + GroupName="a-security-group", Description="First One" + ) + vpc = ec2.create_vpc(CidrBlock="172.28.7.0/24", InstanceTenancy="default") + subnet1 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.192/26", AvailabilityZone=f"{AWS_REGION}a" + ) + subnet2 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.0/26", AvailabilityZone=f"{AWS_REGION}b" + ) + + lb = conn.create_load_balancer( + Name="my-lb", + Subnets=[subnet1.id, subnet2.id], + SecurityGroups=[security_group.id], + Scheme="internal", + )["LoadBalancers"][0] + + conn.create_listener( + LoadBalancerArn=lb["LoadBalancerArn"], + Protocol="HTTP", + DefaultActions=[ + { + "Type": "redirect", + "RedirectConfig": { + "Protocol": "HTTPS", + "Port": "443", + "StatusCode": "HTTP_301", + }, + } + ], + ) + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elbv2.elbv2_service import ELBv2 + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elbv2.elbv2_ssl_listeners.elbv2_ssl_listeners.elbv2_client", + new=ELBv2(current_audit_info), + ): + from providers.aws.services.elbv2.elbv2_ssl_listeners.elbv2_ssl_listeners import ( + elbv2_ssl_listeners, + ) + + check = elbv2_ssl_listeners() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert search( + "has HTTP listener but it redirects to HTTPS", + result[0].status_extended, + ) + assert result[0].resource_id == "my-lb" + assert result[0].resource_arn == lb["LoadBalancerArn"] diff --git a/providers/aws/services/elbv2/elbv2_waf_acl_attached/__init__.py b/providers/aws/services/elbv2/elbv2_waf_acl_attached/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/providers/aws/services/elbv2/elbv2_waf_acl_attached/elbv2_waf_acl_attached.metadata.json b/providers/aws/services/elbv2/elbv2_waf_acl_attached/elbv2_waf_acl_attached.metadata.json new file mode 100644 index 00000000..acefe3e8 --- /dev/null +++ b/providers/aws/services/elbv2/elbv2_waf_acl_attached/elbv2_waf_acl_attached.metadata.json @@ -0,0 +1,35 @@ +{ + "Provider": "aws", + "CheckID": "elbv2_waf_acl_attached", + "CheckTitle": "Check if Application Load Balancer has a WAF ACL attached.", + "CheckType": ["Infrastructure Security"], + "ServiceName": "elbv2", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "Severity": "medium", + "ResourceType": "AwsElasticLoadBalancingV2LoadBalancer", + "Description": "Check if Application Load Balancer has a WAF ACL attached.", + "Risk": "If not WAF ACL is attached risk of web attacks increases.", + "RelatedUrl": "", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "Using the AWS Management Console open the AWS WAF console to attach an ACL.", + "Url": "https://docs.aws.amazon.com/waf/latest/developerguide/web-acl-associating-aws-resource.html" + } + }, + "Categories": [], + "Tags": { + "Tag1Key": "value", + "Tag2Key": "value" + }, + "DependsOn": [], + "RelatedTo": [], + "Notes": "", + "Compliance": [] + } diff --git a/providers/aws/services/elbv2/elbv2_waf_acl_attached/elbv2_waf_acl_attached.py b/providers/aws/services/elbv2/elbv2_waf_acl_attached/elbv2_waf_acl_attached.py new file mode 100644 index 00000000..80205bca --- /dev/null +++ b/providers/aws/services/elbv2/elbv2_waf_acl_attached/elbv2_waf_acl_attached.py @@ -0,0 +1,31 @@ +from lib.check.models import Check, Check_Report +from providers.aws.services.elbv2.elbv2_client import elbv2_client +from providers.aws.services.waf.waf_client import waf_client +from providers.aws.services.wafv2.wafv2_client import wafv2_client + + +class elbv2_waf_acl_attached(Check): + def execute(self): + findings = [] + for lb in elbv2_client.loadbalancersv2: + if lb.type == "application": + report = Check_Report(self.metadata) + report.region = lb.region + report.resource_id = lb.name + report.resource_arn = lb.arn + report.status = "FAIL" + report.status_extended = ( + f"ELBv2 ALB {lb.name} is not protected by WAF Web ACL." + ) + for acl in wafv2_client.web_acls: + if lb.arn in acl.albs: + report.status = "PASS" + report.status_extended = f"ELBv2 ALB {lb.name} is protected by WAFv2 Web ACL {acl.name}." + for acl in waf_client.web_acls: + if lb.arn in acl.albs: + report.status = "PASS" + report.status_extended = f"ELBv2 ALB {lb.name} is protected by WAFv1 Web ACL {acl.name}." + + findings.append(report) + + return findings diff --git a/providers/aws/services/elbv2/elbv2_waf_acl_attached/elbv2_waf_acl_attached_test.py b/providers/aws/services/elbv2/elbv2_waf_acl_attached/elbv2_waf_acl_attached_test.py new file mode 100644 index 00000000..382e8a41 --- /dev/null +++ b/providers/aws/services/elbv2/elbv2_waf_acl_attached/elbv2_waf_acl_attached_test.py @@ -0,0 +1,213 @@ +from re import search +from unittest import mock + +import botocore +from boto3 import client, resource +from moto import mock_ec2, mock_elbv2, mock_wafv2 + +AWS_REGION = "eu-west-1" +AWS_ACCOUNT_NUMBER = "123456789012" + +# Mocking WAF-Regional Calls +make_api_call = botocore.client.BaseClient._make_api_call + + +def mock_make_api_call(self, operation_name, kwarg): + """We have to mock every AWS API call using Boto3""" + if operation_name == "ListWebACLs": + return { + "WebACLs": [ + {"WebACLId": "my-web-acl-id", "Name": "my-web-acl"}, + ] + } + if operation_name == "ListResourcesForWebACL": + return { + "ResourceArns": [ + "alb-arn", + ] + } + + return make_api_call(self, operation_name, kwarg) + + +class Test_elbv2_waf_acl_attached: + @mock_wafv2 + @mock_elbv2 + def test_elb_no_balancers(self): + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elbv2.elbv2_service import ELBv2 + from providers.aws.services.waf.waf_service import WAF + from providers.aws.services.wafv2.wafv2_service import WAFv2 + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elbv2.elbv2_waf_acl_attached.elbv2_waf_acl_attached.elbv2_client", + new=ELBv2(current_audit_info), + ): + with mock.patch( + "providers.aws.services.elbv2.elbv2_waf_acl_attached.elbv2_waf_acl_attached.wafv2_client", + new=WAFv2(current_audit_info), + ): + with mock.patch( + "providers.aws.services.elbv2.elbv2_waf_acl_attached.elbv2_waf_acl_attached.waf_client", + new=WAF(current_audit_info), + ): + # Test Check + from providers.aws.services.elbv2.elbv2_waf_acl_attached.elbv2_waf_acl_attached import ( + elbv2_waf_acl_attached, + ) + + check = elbv2_waf_acl_attached() + result = check.execute() + + assert len(result) == 0 + + @mock_wafv2 + @mock_ec2 + @mock_elbv2 + def test_elbv2_without_WAF(self): + conn = client("elbv2", region_name=AWS_REGION) + ec2 = resource("ec2", region_name=AWS_REGION) + wafv2 = client("wafv2", region_name="us-east-1") + _ = wafv2.create_web_acl( + Scope="REGIONAL", + Name="my-web-acl", + DefaultAction={"Allow": {}}, + VisibilityConfig={ + "SampledRequestsEnabled": False, + "CloudWatchMetricsEnabled": False, + "MetricName": "idk", + }, + )["Summary"] + security_group = ec2.create_security_group( + GroupName="a-security-group", Description="First One" + ) + vpc = ec2.create_vpc(CidrBlock="172.28.7.0/24", InstanceTenancy="default") + subnet1 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.192/26", AvailabilityZone=f"{AWS_REGION}a" + ) + subnet2 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.0/26", AvailabilityZone=f"{AWS_REGION}b" + ) + + lb = conn.create_load_balancer( + Name="my-lb", + Subnets=[subnet1.id, subnet2.id], + SecurityGroups=[security_group.id], + Scheme="internal", + Type="application", + )["LoadBalancers"][0] + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elbv2.elbv2_service import ELBv2 + from providers.aws.services.waf.waf_service import WAF + from providers.aws.services.wafv2.wafv2_service import WAFv2 + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elbv2.elbv2_waf_acl_attached.elbv2_waf_acl_attached.elbv2_client", + new=ELBv2(current_audit_info), + ): + with mock.patch( + "providers.aws.services.elbv2.elbv2_waf_acl_attached.elbv2_waf_acl_attached.wafv2_client", + new=WAFv2(current_audit_info), + ): + with mock.patch( + "providers.aws.services.elbv2.elbv2_waf_acl_attached.elbv2_waf_acl_attached.waf_client", + new=WAF(current_audit_info), + ): + # Test Check + from providers.aws.services.elbv2.elbv2_waf_acl_attached.elbv2_waf_acl_attached import ( + elbv2_waf_acl_attached, + ) + + check = elbv2_waf_acl_attached() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert search( + "is not protected by WAF Web ACL", + result[0].status_extended, + ) + assert result[0].resource_id == "my-lb" + assert result[0].resource_arn == lb["LoadBalancerArn"] + + @mock_wafv2 + @mock_ec2 + @mock_elbv2 + def test_elbv2_with_WAF(self): + conn = client("elbv2", region_name=AWS_REGION) + ec2 = resource("ec2", region_name=AWS_REGION) + wafv2 = client("wafv2", region_name="us-east-1") + waf = wafv2.create_web_acl( + Scope="REGIONAL", + Name="my-web-acl", + DefaultAction={"Allow": {}}, + VisibilityConfig={ + "SampledRequestsEnabled": False, + "CloudWatchMetricsEnabled": False, + "MetricName": "idk", + }, + )["Summary"] + security_group = ec2.create_security_group( + GroupName="a-security-group", Description="First One" + ) + vpc = ec2.create_vpc(CidrBlock="172.28.7.0/24", InstanceTenancy="default") + subnet1 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.192/26", AvailabilityZone=f"{AWS_REGION}a" + ) + subnet2 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.0/26", AvailabilityZone=f"{AWS_REGION}b" + ) + + lb = conn.create_load_balancer( + Name="my-lb", + Subnets=[subnet1.id, subnet2.id], + SecurityGroups=[security_group.id], + Scheme="internal", + Type="application", + )["LoadBalancers"][0] + + wafv2.associate_web_acl(WebACLArn=waf["ARN"], ResourceArn=lb["LoadBalancerArn"]) + + from providers.aws.lib.audit_info.audit_info import current_audit_info + from providers.aws.services.elbv2.elbv2_service import ELBv2 + from providers.aws.services.waf.waf_service import WAF + from providers.aws.services.wafv2.wafv2_service import WAFv2 + + current_audit_info.audited_partition = "aws" + + with mock.patch( + "providers.aws.services.elbv2.elbv2_waf_acl_attached.elbv2_waf_acl_attached.elbv2_client", + new=ELBv2(current_audit_info), + ): + with mock.patch( + "providers.aws.services.elbv2.elbv2_waf_acl_attached.elbv2_waf_acl_attached.wafv2_client", + new=WAFv2(current_audit_info), + ) as service_client: + with mock.patch( + "providers.aws.services.elbv2.elbv2_waf_acl_attached.elbv2_waf_acl_attached.waf_client", + new=WAF(current_audit_info), + ): + # Test Check + from providers.aws.services.elbv2.elbv2_waf_acl_attached.elbv2_waf_acl_attached import ( + elbv2_waf_acl_attached, + ) + + service_client.web_acls[0].albs.append(lb["LoadBalancerArn"]) + + check = elbv2_waf_acl_attached() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert search( + "is protected by WAFv2 Web ACL", + result[0].status_extended, + ) + assert result[0].resource_id == "my-lb" + assert result[0].resource_arn == lb["LoadBalancerArn"] diff --git a/providers/aws/services/iam/iam_disable_45_days_credentials/iam_disable_45_days_credentials_test.py b/providers/aws/services/iam/iam_disable_45_days_credentials/iam_disable_45_days_credentials_test.py index e85e74ff..9eb02f00 100644 --- a/providers/aws/services/iam/iam_disable_45_days_credentials/iam_disable_45_days_credentials_test.py +++ b/providers/aws/services/iam/iam_disable_45_days_credentials/iam_disable_45_days_credentials_test.py @@ -84,7 +84,6 @@ class Test_iam_disable_45_days_credentials_test: ) service_client.users[0].password_last_used = "" - print(service_client.users) # raise Exception check = iam_disable_45_days_credentials() result = check.execute() diff --git a/providers/aws/services/iam/iam_no_expired_server_certificates_stored/iam_no_expired_server_certificates_stored.py b/providers/aws/services/iam/iam_no_expired_server_certificates_stored/iam_no_expired_server_certificates_stored.py index 22acbd5e..edd87024 100644 --- a/providers/aws/services/iam/iam_no_expired_server_certificates_stored/iam_no_expired_server_certificates_stored.py +++ b/providers/aws/services/iam/iam_no_expired_server_certificates_stored/iam_no_expired_server_certificates_stored.py @@ -14,7 +14,6 @@ class iam_no_expired_server_certificates_stored(Check): report.resource_id = certificate.id report.resource_arn = certificate.arn expiration_days = (datetime.now(timezone.utc) - certificate.expiration).days - print(certificate.expiration) if expiration_days >= 0: report.status = "FAIL" report.status_extended = f"IAM Certificate {certificate.name} has expired {expiration_days} days ago." diff --git a/providers/aws/services/waf/__init__.py b/providers/aws/services/waf/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/providers/aws/services/waf/waf_client.py b/providers/aws/services/waf/waf_client.py new file mode 100644 index 00000000..781a15c4 --- /dev/null +++ b/providers/aws/services/waf/waf_client.py @@ -0,0 +1,4 @@ +from providers.aws.lib.audit_info.audit_info import current_audit_info +from providers.aws.services.waf.waf_service import WAF + +waf_client = WAF(current_audit_info) diff --git a/providers/aws/services/waf/waf_service.py b/providers/aws/services/waf/waf_service.py new file mode 100644 index 00000000..3d0b813d --- /dev/null +++ b/providers/aws/services/waf/waf_service.py @@ -0,0 +1,68 @@ +import threading + +from pydantic import BaseModel + +from lib.logger import logger +from providers.aws.aws_provider import generate_regional_clients + + +################### WAF +class WAF: + def __init__(self, audit_info): + self.service = "waf-regional" + self.session = audit_info.audit_session + self.regional_clients = generate_regional_clients(self.service, audit_info) + self.web_acls = [] + self.__threading_call__(self.__list_web_acls__) + self.__threading_call__(self.__list_resources_for_web_acl__) + + def __get_session__(self): + return self.session + + def __threading_call__(self, call): + threads = [] + for regional_client in self.regional_clients.values(): + threads.append(threading.Thread(target=call, args=(regional_client,))) + for t in threads: + t.start() + for t in threads: + t.join() + + def __list_web_acls__(self, regional_client): + logger.info("WAF - Listing Regional Web ACLs...") + try: + for waf in regional_client.list_web_acls()["WebACLs"]: + self.web_acls.append( + WebAcl( + name=waf["Name"], + id=waf["WebACLId"], + albs=[], + region=regional_client.region, + ) + ) + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + def __list_resources_for_web_acl__(self, regional_client): + logger.info("WAF - Describing resources...") + try: + for acl in self.web_acls: + if acl.region == regional_client.region: + for resource in regional_client.list_resources_for_web_acl( + WebACLId=acl.id, ResourceType="APPLICATION_LOAD_BALANCER" + )["ResourceArns"]: + acl.albs.append(resource) + + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + +class WebAcl(BaseModel): + name: str + id: str + albs: list[str] + region: str diff --git a/providers/aws/services/waf/waf_service_test.py b/providers/aws/services/waf/waf_service_test.py new file mode 100644 index 00000000..8a8aacc0 --- /dev/null +++ b/providers/aws/services/waf/waf_service_test.py @@ -0,0 +1,108 @@ +from unittest.mock import patch + +import botocore +from boto3 import session + +from providers.aws.lib.audit_info.models import AWS_Audit_Info +from providers.aws.services.waf.waf_service import WAF + +AWS_ACCOUNT_NUMBER = 123456789012 +AWS_REGION = "us-east-1" + +# Mocking WAF-Regional Calls +make_api_call = botocore.client.BaseClient._make_api_call + + +def mock_make_api_call(self, operation_name, kwarg): + """We have to mock every AWS API call using Boto3""" + if operation_name == "ListWebACLs": + return { + "WebACLs": [ + {"WebACLId": "my-web-acl-id", "Name": "my-web-acl"}, + ] + } + if operation_name == "ListResourcesForWebACL": + return { + "ResourceArns": [ + "alb-arn", + ] + } + + return make_api_call(self, operation_name, kwarg) + + +# Mock generate_regional_clients() +def mock_generate_regional_clients(service, audit_info): + regional_client = audit_info.audit_session.client(service, region_name=AWS_REGION) + regional_client.region = AWS_REGION + return {AWS_REGION: regional_client} + + +# Patch every AWS call using Boto3 and generate_regional_clients to have 1 client +@patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) +@patch( + "providers.aws.services.waf.waf_service.generate_regional_clients", + new=mock_generate_regional_clients, +) +class Test_WAF_Service: + # Mocked Audit Info + def set_mocked_audit_info(self): + audit_info = AWS_Audit_Info( + 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=None, + organizations_metadata=None, + ) + return audit_info + + # Test WAF Service + def test_service(self): + # WAF client for this test class + audit_info = self.set_mocked_audit_info() + waf = WAF(audit_info) + assert waf.service == "waf-regional" + + # Test WAF Client + def test_client(self): + # WAF client for this test class + audit_info = self.set_mocked_audit_info() + waf = WAF(audit_info) + for regional_client in waf.regional_clients.values(): + assert regional_client.__class__.__name__ == "WAFRegional" + + # Test WAF Session + def test__get_session__(self): + # WAF client for this test class + audit_info = self.set_mocked_audit_info() + waf = WAF(audit_info) + assert waf.session.__class__.__name__ == "Session" + + # Test WAF Describe Web ACLs + def test__list_web_acls__(self): + # WAF client for this test class + audit_info = self.set_mocked_audit_info() + waf = WAF(audit_info) + assert len(waf.web_acls) == 1 + assert waf.web_acls[0].name == "my-web-acl" + assert waf.web_acls[0].region == AWS_REGION + assert waf.web_acls[0].id == "my-web-acl-id" + + # Test WAF Describe Web ACLs Resources + def test__list_resources_for_web_acl__(self): + # WAF client for this test class + audit_info = self.set_mocked_audit_info() + waf = WAF(audit_info) + assert len(waf.web_acls) == 1 + assert len(waf.web_acls[0].albs) == 1 + assert "alb-arn" in waf.web_acls[0].albs diff --git a/providers/aws/services/wafv2/__init__.py b/providers/aws/services/wafv2/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/providers/aws/services/wafv2/wafv2_client.py b/providers/aws/services/wafv2/wafv2_client.py new file mode 100644 index 00000000..ecad1a3f --- /dev/null +++ b/providers/aws/services/wafv2/wafv2_client.py @@ -0,0 +1,4 @@ +from providers.aws.lib.audit_info.audit_info import current_audit_info +from providers.aws.services.wafv2.wafv2_service import WAFv2 + +wafv2_client = WAFv2(current_audit_info) diff --git a/providers/aws/services/wafv2/wafv2_service.py b/providers/aws/services/wafv2/wafv2_service.py new file mode 100644 index 00000000..abd7416a --- /dev/null +++ b/providers/aws/services/wafv2/wafv2_service.py @@ -0,0 +1,70 @@ +import threading + +from pydantic import BaseModel + +from lib.logger import logger +from providers.aws.aws_provider import generate_regional_clients + + +################### WAFv2 +class WAFv2: + def __init__(self, audit_info): + self.service = "wafv2" + self.session = audit_info.audit_session + self.regional_clients = generate_regional_clients(self.service, audit_info) + self.web_acls = [] + self.__threading_call__(self.__list_web_acls__) + self.__threading_call__(self.__list_resources_for_web_acl__) + + def __get_session__(self): + return self.session + + def __threading_call__(self, call): + threads = [] + for regional_client in self.regional_clients.values(): + threads.append(threading.Thread(target=call, args=(regional_client,))) + for t in threads: + t.start() + for t in threads: + t.join() + + def __list_web_acls__(self, regional_client): + logger.info("WAFv2 - Listing Regional Web ACLs...") + try: + for wafv2 in regional_client.list_web_acls(Scope="REGIONAL")["WebACLs"]: + self.web_acls.append( + WebAclv2( + arn=wafv2["ARN"], + name=wafv2["Name"], + id=wafv2["Id"], + albs=[], + region=regional_client.region, + ) + ) + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + def __list_resources_for_web_acl__(self, regional_client): + logger.info("WAFv2 - Describing resources...") + try: + for acl in self.web_acls: + if acl.region == regional_client.region: + for resource in regional_client.list_resources_for_web_acl( + WebACLArn=acl.arn, ResourceType="APPLICATION_LOAD_BALANCER" + )["ResourceArns"]: + acl.albs.append(resource) + + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + +class WebAclv2(BaseModel): + arn: str + name: str + id: str + albs: list[str] + region: str diff --git a/providers/aws/services/wafv2/wafv2_service_test.py b/providers/aws/services/wafv2/wafv2_service_test.py new file mode 100644 index 00000000..ee9d7fdc --- /dev/null +++ b/providers/aws/services/wafv2/wafv2_service_test.py @@ -0,0 +1,125 @@ +from boto3 import client, resource, session +from moto import mock_ec2, mock_elbv2, mock_wafv2 + +from providers.aws.lib.audit_info.models import AWS_Audit_Info +from providers.aws.services.wafv2.wafv2_service import WAFv2 + +AWS_ACCOUNT_NUMBER = 123456789012 +AWS_REGION = "us-east-1" + + +class Test_WAFv2_Service: + # Mocked Audit Info + def set_mocked_audit_info(self): + audit_info = AWS_Audit_Info( + 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=None, + organizations_metadata=None, + ) + return audit_info + + # Test WAFv2 Service + @mock_wafv2 + def test_service(self): + # WAFv2 client for this test class + audit_info = self.set_mocked_audit_info() + wafv2 = WAFv2(audit_info) + assert wafv2.service == "wafv2" + + # Test WAFv2 Client + @mock_wafv2 + def test_client(self): + # WAFv2 client for this test class + audit_info = self.set_mocked_audit_info() + wafv2 = WAFv2(audit_info) + for regional_client in wafv2.regional_clients.values(): + assert regional_client.__class__.__name__ == "WAFV2" + + # Test WAFv2 Session + @mock_wafv2 + def test__get_session__(self): + # WAFv2 client for this test class + audit_info = self.set_mocked_audit_info() + wafv2 = WAFv2(audit_info) + assert wafv2.session.__class__.__name__ == "Session" + + # Test WAFv2 Describe Web ACLs + @mock_wafv2 + def test__list_web_acls__(self): + wafv2 = client("wafv2", region_name="us-east-1") + waf = wafv2.create_web_acl( + Scope="REGIONAL", + Name="my-web-acl", + DefaultAction={"Allow": {}}, + VisibilityConfig={ + "SampledRequestsEnabled": False, + "CloudWatchMetricsEnabled": False, + "MetricName": "idk", + }, + )["Summary"] + # WAFv2 client for this test class + audit_info = self.set_mocked_audit_info() + wafv2 = WAFv2(audit_info) + assert len(wafv2.web_acls) == 1 + assert wafv2.web_acls[0].name == waf["Name"] + assert wafv2.web_acls[0].region == AWS_REGION + assert wafv2.web_acls[0].arn == waf["ARN"] + assert wafv2.web_acls[0].id == waf["Id"] + + # Test WAFv2 Describe Web ACLs Resources + @mock_ec2 + @mock_elbv2 + @mock_wafv2 + def test__list_resources_for_web_acl__(self): + wafv2 = client("wafv2", region_name="us-east-1") + conn = client("elbv2", region_name=AWS_REGION) + ec2 = resource("ec2", region_name=AWS_REGION) + waf = wafv2.create_web_acl( + Scope="REGIONAL", + Name="my-web-acl", + DefaultAction={"Allow": {}}, + VisibilityConfig={ + "SampledRequestsEnabled": False, + "CloudWatchMetricsEnabled": False, + "MetricName": "idk", + }, + )["Summary"] + security_group = ec2.create_security_group( + GroupName="a-security-group", Description="First One" + ) + vpc = ec2.create_vpc(CidrBlock="172.28.7.0/24", InstanceTenancy="default") + subnet1 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.192/26", AvailabilityZone=f"{AWS_REGION}a" + ) + subnet2 = ec2.create_subnet( + VpcId=vpc.id, CidrBlock="172.28.7.0/26", AvailabilityZone=f"{AWS_REGION}b" + ) + + lb = conn.create_load_balancer( + Name="my-lb", + Subnets=[subnet1.id, subnet2.id], + SecurityGroups=[security_group.id], + Scheme="internal", + Type="application", + )["LoadBalancers"][0] + + wafv2.associate_web_acl(WebACLArn=waf["ARN"], ResourceArn=lb["LoadBalancerArn"]) + # WAFv2 client for this test class + audit_info = self.set_mocked_audit_info() + wafv2 = WAFv2(audit_info) + wafv2.web_acls[0].albs.append(lb["LoadBalancerArn"]) + assert len(wafv2.web_acls) == 1 + assert len(wafv2.web_acls[0].albs) == 1 + assert lb["LoadBalancerArn"] in wafv2.web_acls[0].albs