mirror of
https://github.com/ghndrx/prowler.git
synced 2026-02-10 06:45:08 +00:00
feat(ELB): add ELB and ELBv2 tests and checks (#1489)
Co-authored-by: sergargar <sergio@verica.io>
This commit is contained in:
@@ -309,6 +309,10 @@ def generate_regional_clients(service: str, audit_info: AWS_Audit_Info) -> dict:
|
|||||||
json_regions = data["services"]["dynamodb"]["regions"][
|
json_regions = data["services"]["dynamodb"]["regions"][
|
||||||
audit_info.audited_partition
|
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:
|
else:
|
||||||
json_regions = data["services"][service]["regions"][
|
json_regions = data["services"][service]["regions"][
|
||||||
audit_info.audited_partition
|
audit_info.audited_partition
|
||||||
|
|||||||
0
providers/aws/services/elb/__init__.py
Normal file
0
providers/aws/services/elb/__init__.py
Normal file
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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 <alb arn>\
|
|
||||||
# --attributes Key=routing.http.desync_mitigation_mode,Value=<defensive/strictest>
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
4
providers/aws/services/elb/elb_client.py
Normal file
4
providers/aws/services/elb/elb_client.py
Normal file
@@ -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)
|
||||||
@@ -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 <lb_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": []
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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"
|
||||||
@@ -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": []
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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"
|
||||||
@@ -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 <lb_name> --load-balancer-attributes '{AccessLog:{Enabled:true,EmitInterval:60,S3BucketName:<bucket_name>}}'",
|
||||||
|
"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": []
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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"
|
||||||
91
providers/aws/services/elb/elb_service.py
Normal file
91
providers/aws/services/elb/elb_service.py
Normal file
@@ -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]
|
||||||
126
providers/aws/services/elb/elb_service_test.py
Normal file
126
providers/aws/services/elb/elb_service_test.py
Normal file
@@ -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
|
||||||
@@ -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 <lb_name> --listeners Protocol=HTTPS, LoadBalancerPort=443, InstanceProtocol=HTTP, InstancePort=80, SSLCertificateId=<certificate_arn>",
|
||||||
|
"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": []
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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"
|
||||||
0
providers/aws/services/elbv2/__init__.py
Normal file
0
providers/aws/services/elbv2/__init__.py
Normal file
4
providers/aws/services/elbv2/elbv2_client.py
Normal file
4
providers/aws/services/elbv2/elbv2_client.py
Normal file
@@ -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)
|
||||||
@@ -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 <lb_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": []
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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"]
|
||||||
@@ -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 <alb arn> --attributes Key=routing.http.desync_mitigation_mode,Value=<defensive/strictest>",
|
||||||
|
"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": []
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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"]
|
||||||
@@ -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 <lb_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": []
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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"]
|
||||||
@@ -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": []
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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"]
|
||||||
@@ -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": []
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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"]
|
||||||
@@ -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 <lb_arn> --attributes Key=access_logs.s3.enabled,Value=true Key=access_logs.s3.bucket,Value=<bucket_name> Key=access_logs.s3.prefix,Value=<prefix>",
|
||||||
|
"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": []
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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"]
|
||||||
@@ -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 <lb_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": []
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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"]
|
||||||
162
providers/aws/services/elbv2/elbv2_service.py
Normal file
162
providers/aws/services/elbv2/elbv2_service.py
Normal file
@@ -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]
|
||||||
228
providers/aws/services/elbv2/elbv2_service_test.py
Normal file
228
providers/aws/services/elbv2/elbv2_service_test.py
Normal file
@@ -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
|
||||||
@@ -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 <lb_arn> --protocol HTTPS --port 443 --ssl-policy <ssl_policy> --certificates CertificateArn=<certificate_arn>,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": []
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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"]
|
||||||
@@ -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": []
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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"]
|
||||||
@@ -84,7 +84,6 @@ class Test_iam_disable_45_days_credentials_test:
|
|||||||
)
|
)
|
||||||
|
|
||||||
service_client.users[0].password_last_used = ""
|
service_client.users[0].password_last_used = ""
|
||||||
print(service_client.users)
|
|
||||||
# raise Exception
|
# raise Exception
|
||||||
check = iam_disable_45_days_credentials()
|
check = iam_disable_45_days_credentials()
|
||||||
result = check.execute()
|
result = check.execute()
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ class iam_no_expired_server_certificates_stored(Check):
|
|||||||
report.resource_id = certificate.id
|
report.resource_id = certificate.id
|
||||||
report.resource_arn = certificate.arn
|
report.resource_arn = certificate.arn
|
||||||
expiration_days = (datetime.now(timezone.utc) - certificate.expiration).days
|
expiration_days = (datetime.now(timezone.utc) - certificate.expiration).days
|
||||||
print(certificate.expiration)
|
|
||||||
if expiration_days >= 0:
|
if expiration_days >= 0:
|
||||||
report.status = "FAIL"
|
report.status = "FAIL"
|
||||||
report.status_extended = f"IAM Certificate {certificate.name} has expired {expiration_days} days ago."
|
report.status_extended = f"IAM Certificate {certificate.name} has expired {expiration_days} days ago."
|
||||||
|
|||||||
0
providers/aws/services/waf/__init__.py
Normal file
0
providers/aws/services/waf/__init__.py
Normal file
4
providers/aws/services/waf/waf_client.py
Normal file
4
providers/aws/services/waf/waf_client.py
Normal file
@@ -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)
|
||||||
68
providers/aws/services/waf/waf_service.py
Normal file
68
providers/aws/services/waf/waf_service.py
Normal file
@@ -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
|
||||||
108
providers/aws/services/waf/waf_service_test.py
Normal file
108
providers/aws/services/waf/waf_service_test.py
Normal file
@@ -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
|
||||||
0
providers/aws/services/wafv2/__init__.py
Normal file
0
providers/aws/services/wafv2/__init__.py
Normal file
4
providers/aws/services/wafv2/wafv2_client.py
Normal file
4
providers/aws/services/wafv2/wafv2_client.py
Normal file
@@ -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)
|
||||||
70
providers/aws/services/wafv2/wafv2_service.py
Normal file
70
providers/aws/services/wafv2/wafv2_service.py
Normal file
@@ -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
|
||||||
125
providers/aws/services/wafv2/wafv2_service_test.py
Normal file
125
providers/aws/services/wafv2/wafv2_service_test.py
Normal file
@@ -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
|
||||||
Reference in New Issue
Block a user