mirror of
https://github.com/ghndrx/prowler.git
synced 2026-02-10 14:55:00 +00:00
feat(Route53): Service and checks (#1493)
Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
This commit is contained in:
0
providers/aws/services/route53/__init__.py
Normal file
0
providers/aws/services/route53/__init__.py
Normal file
@@ -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/route53domains/update-domain-contact-privacy.html
|
|
||||||
#
|
|
||||||
# update-domain-contact-privacy \
|
|
||||||
# --region us-east-1 \
|
|
||||||
# --domain-name example.com \
|
|
||||||
# --admin-privacy \
|
|
||||||
# --registrant-privacy \
|
|
||||||
# --tech-privacy
|
|
||||||
|
|
||||||
CHECK_ID_extra7152="7.152"
|
|
||||||
CHECK_TITLE_extra7152="[extra7152] Enable Privacy Protection for for a Route53 Domain (us-east-1 only)"
|
|
||||||
CHECK_SCORED_extra7152="NOT_SCORED"
|
|
||||||
CHECK_CIS_LEVEL_extra7152="EXTRA"
|
|
||||||
CHECK_SEVERITY_extra7152="Medium"
|
|
||||||
CHECK_ASFF_RESOURCE_TYPE_extra7152="AwsRoute53Domain"
|
|
||||||
CHECK_ALTERNATE_check7152="extra7152"
|
|
||||||
CHECK_SERVICENAME_extra7152="route53"
|
|
||||||
CHECK_RISK_extra7152='Without privacy protection enabled; ones personal information is published to the public WHOIS database'
|
|
||||||
CHECK_REMEDIATION_extra7152='Ensure default Privacy is enabled'
|
|
||||||
CHECK_DOC_extra7152='https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/domain-privacy-protection.html'
|
|
||||||
CHECK_CAF_EPIC_extra7152='Data Protection'
|
|
||||||
|
|
||||||
extra7152(){
|
|
||||||
# Route53 is a global service, looking for domains in US-EAST-1
|
|
||||||
# this is also valid for GovCloud https://docs.aws.amazon.com/govcloud-us/latest/UserGuide/setting-up-route53.html
|
|
||||||
DOMAIN_NAMES=$($AWSCLI route53domains list-domains $PROFILE_OPT --region us-east-1 --query 'Domains[*].DomainName' --output text )
|
|
||||||
if [[ $DOMAIN_NAMES ]];then
|
|
||||||
for domain_name in $DOMAIN_NAMES;do
|
|
||||||
DOMAIN_DETAIL=$($AWSCLI route53domains get-domain-detail $PROFILE_OPT --region us-east-1 --query 'AdminPrivacy' --domain-name $domain_name)
|
|
||||||
if [[ $DOMAIN_DETAIL == false ]]; then
|
|
||||||
textFail "us-east-1: Contact information public for: $domain_name" "us-east-1" "$domain_name"
|
|
||||||
else
|
|
||||||
textPass "us-east-1: All contact information is private for: $domain_name" "us-east-1" "$domain_name"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
else
|
|
||||||
textInfo "us-east-1: No Domain Names found" "us-east-1"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
@@ -1,52 +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/route53domains/enable-domain-transfer-lock.html
|
|
||||||
#
|
|
||||||
# enable-domain-transfer-lock \
|
|
||||||
# --domain-name example.com
|
|
||||||
|
|
||||||
|
|
||||||
CHECK_ID_extra7153="7.153"
|
|
||||||
CHECK_TITLE_extra7153="[extra7153] Enable Transfer Lock for a Route53 Domain (us-east-1 only)"
|
|
||||||
CHECK_SCORED_extra7153="NOT_SCORED"
|
|
||||||
CHECK_CIS_LEVEL_extra7153="EXTRA"
|
|
||||||
CHECK_SEVERITY_extra7153="Medium"
|
|
||||||
CHECK_ASFF_RESOURCE_TYPE_extra7153="AwsRoute53Domain"
|
|
||||||
CHECK_ALTERNATE_check7153="extra7153"
|
|
||||||
CHECK_SERVICENAME_extra7153="route53"
|
|
||||||
CHECK_RISK_extra7153='Without transfer lock enabled; a domain name could be incorrectly moved to a new registrar'
|
|
||||||
CHECK_REMEDIATION_extra7153='Ensure transfer lock is enabled'
|
|
||||||
CHECK_DOC_extra7153='https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/domain-lock.html'
|
|
||||||
CHECK_CAF_EPIC_extra7153='Data Protection'
|
|
||||||
|
|
||||||
extra7153(){
|
|
||||||
# Route53 is a global service, looking for domains in US-EAST-1
|
|
||||||
# this is also valid for GovCloud https://docs.aws.amazon.com/govcloud-us/latest/UserGuide/setting-up-route53.html
|
|
||||||
DOMAIN_NAMES=$($AWSCLI route53domains list-domains $PROFILE_OPT --region us-east-1 --query 'Domains[*].DomainName' --output text )
|
|
||||||
if [[ $DOMAIN_NAMES ]];then
|
|
||||||
for domain_name in $DOMAIN_NAMES;do
|
|
||||||
DOMAIN_DETAIL=$($AWSCLI route53domains get-domain-detail $PROFILE_OPT --region us-east-1 --query 'StatusList' --domain-name $domain_name)
|
|
||||||
HAS_TRANSFER_LOCK=$( grep -o 'clientTransferProhibited' <<< $DOMAIN_DETAIL)
|
|
||||||
if [[ $HAS_TRANSFER_LOCK ]]; then
|
|
||||||
textPass "us-east-1: clientTransferProhibited found for: $domain_name" "us-east-1" "$domain_name"
|
|
||||||
else
|
|
||||||
textFail "us-east-1: clientTransferProhibited not found for: $domain_name" "us-east-1" "$domain_name"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
else
|
|
||||||
textInfo "us-east-1: No Domain Names found" "us-east-1"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
@@ -1,41 +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_extra719="7.19"
|
|
||||||
CHECK_TITLE_extra719="[extra719] Check if Route53 public hosted zones are logging queries to CloudWatch Logs"
|
|
||||||
CHECK_SCORED_extra719="NOT_SCORED"
|
|
||||||
CHECK_CIS_LEVEL_extra719="EXTRA"
|
|
||||||
CHECK_SEVERITY_extra719="Medium"
|
|
||||||
CHECK_ALTERNATE_check719="extra719"
|
|
||||||
CHECK_ASFF_RESOURCE_TYPE_extra719="AwsRoute53HostedZone"
|
|
||||||
CHECK_SERVICENAME_extra719="route53"
|
|
||||||
CHECK_RISK_extra719='If logs are not enabled; monitoring of service use and threat analysis is not possible.'
|
|
||||||
CHECK_REMEDIATION_extra719='Enable CloudWatch logs and define metrics and uses cases for the events recorded.'
|
|
||||||
CHECK_DOC_extra719='https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/monitoring-hosted-zones-with-cloudwatch.html'
|
|
||||||
CHECK_CAF_EPIC_extra719='Logging and Monitoring'
|
|
||||||
|
|
||||||
extra719(){
|
|
||||||
# You can't create a query logging config for a private hosted zone.
|
|
||||||
LIST_OF_HOSTED_ZONES=$($AWSCLI route53 list-hosted-zones $PROFILE_OPT | jq -r ".HostedZones[] | select(.Config.PrivateZone == false) | .Id")
|
|
||||||
if [[ $LIST_OF_HOSTED_ZONES ]]; then
|
|
||||||
for hostedzoneid in $LIST_OF_HOSTED_ZONES;do
|
|
||||||
HOSTED_ZONE_QUERY_LOG_ENABLED=$($AWSCLI route53 list-query-logging-configs --hosted-zone-id $hostedzoneid $PROFILE_OPT --query QueryLoggingConfigs[*].CloudWatchLogsLogGroupArn --output text|cut -d: -f7)
|
|
||||||
if [[ $HOSTED_ZONE_QUERY_LOG_ENABLED ]];then
|
|
||||||
textPass "$REGION: Route53 public hosted zone Id $hostedzoneid has query logging enabled in Log Group $HOSTED_ZONE_QUERY_LOG_ENABLED" "$REGION" "$hostedzoneid"
|
|
||||||
else
|
|
||||||
textFail "$REGION: Route53 public hosted zone Id $hostedzoneid has query logging disabled!" "$REGION" "$hostedzoneid"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
else
|
|
||||||
textInfo "$REGION: No Route53 hosted zones found" "$REGION"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
4
providers/aws/services/route53/route53_client.py
Normal file
4
providers/aws/services/route53/route53_client.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
from providers.aws.lib.audit_info.audit_info import current_audit_info
|
||||||
|
from providers.aws.services.route53.route53_service import Route53
|
||||||
|
|
||||||
|
route53_client = Route53(current_audit_info)
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"Provider": "aws",
|
||||||
|
"CheckID": "route53_domains_privacy_protection_enabled",
|
||||||
|
"CheckTitle": "Enable Privacy Protection for for a Route53 Domain.",
|
||||||
|
"CheckType": [],
|
||||||
|
"ServiceName": "route53",
|
||||||
|
"SubServiceName": "",
|
||||||
|
"ResourceIdTemplate": "",
|
||||||
|
"Severity": "medium",
|
||||||
|
"ResourceType": "AwsRoute53Domain",
|
||||||
|
"Description": "Enable Privacy Protection for for a Route53 Domain.",
|
||||||
|
"Risk": "Without privacy protection enabled, ones personal information is published to the public WHOIS database.",
|
||||||
|
"RelatedUrl": "https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/domain-privacy-protection.html",
|
||||||
|
"Remediation": {
|
||||||
|
"Code": {
|
||||||
|
"CLI": "aws route53domains update-domain-contact-privacy --domain-name domain.com --registrant-privacy",
|
||||||
|
"NativeIaC": "",
|
||||||
|
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/Route53/privacy-protection.html",
|
||||||
|
"Terraform": ""
|
||||||
|
},
|
||||||
|
"Recommendation": {
|
||||||
|
"Text": "Ensure default Privacy is enabled.",
|
||||||
|
"Url": "https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/domain-privacy-protection.html"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Categories": [],
|
||||||
|
"Tags": {
|
||||||
|
"Tag1Key": "value",
|
||||||
|
"Tag2Key": "value"
|
||||||
|
},
|
||||||
|
"DependsOn": [],
|
||||||
|
"RelatedTo": [],
|
||||||
|
"Notes": "",
|
||||||
|
"Compliance": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
from lib.check.models import Check, Check_Report
|
||||||
|
from providers.aws.services.route53.route53domains_client import route53domains_client
|
||||||
|
|
||||||
|
|
||||||
|
class route53_domains_privacy_protection_enabled(Check):
|
||||||
|
def execute(self) -> Check_Report:
|
||||||
|
findings = []
|
||||||
|
|
||||||
|
for domain in route53domains_client.domains.values():
|
||||||
|
report = Check_Report(self.metadata)
|
||||||
|
report.resource_id = domain.name
|
||||||
|
report.region = domain.region
|
||||||
|
|
||||||
|
if domain.admin_privacy:
|
||||||
|
report.status = "PASS"
|
||||||
|
report.status_extended = (
|
||||||
|
f"Contact information is private for the {domain.name} domain"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
report.status = "FAIL"
|
||||||
|
report.status_extended = (
|
||||||
|
f"Contact information is public for the {domain.name} domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
findings.append(report)
|
||||||
|
|
||||||
|
return findings
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
from providers.aws.services.route53.route53_service import Domain
|
||||||
|
|
||||||
|
AWS_REGION = "us-east-1"
|
||||||
|
|
||||||
|
|
||||||
|
class Test_route53_domains_privacy_protection_enabled:
|
||||||
|
def test_no_domains(self):
|
||||||
|
route53domains = mock.MagicMock
|
||||||
|
route53domains.domains = {}
|
||||||
|
|
||||||
|
with mock.patch(
|
||||||
|
"providers.aws.services.route53.route53_service.Route53Domains",
|
||||||
|
new=route53domains,
|
||||||
|
):
|
||||||
|
# Test Check
|
||||||
|
from providers.aws.services.route53.route53_domains_privacy_protection_enabled.route53_domains_privacy_protection_enabled import (
|
||||||
|
route53_domains_privacy_protection_enabled,
|
||||||
|
)
|
||||||
|
|
||||||
|
check = route53_domains_privacy_protection_enabled()
|
||||||
|
result = check.execute()
|
||||||
|
|
||||||
|
assert len(result) == 0
|
||||||
|
|
||||||
|
def test_domain_privacy_protection_disabled(self):
|
||||||
|
route53domains = mock.MagicMock
|
||||||
|
domain_name = "test-domain.com"
|
||||||
|
route53domains.domains = {
|
||||||
|
domain_name: Domain(
|
||||||
|
name=domain_name, region=AWS_REGION, admin_privacy=False
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
with mock.patch(
|
||||||
|
"providers.aws.services.route53.route53_service.Route53Domains",
|
||||||
|
new=route53domains,
|
||||||
|
):
|
||||||
|
# Test Check
|
||||||
|
from providers.aws.services.route53.route53_domains_privacy_protection_enabled.route53_domains_privacy_protection_enabled import (
|
||||||
|
route53_domains_privacy_protection_enabled,
|
||||||
|
)
|
||||||
|
|
||||||
|
check = route53_domains_privacy_protection_enabled()
|
||||||
|
result = check.execute()
|
||||||
|
|
||||||
|
assert len(result) == 1
|
||||||
|
assert result[0].resource_id == domain_name
|
||||||
|
assert result[0].region == AWS_REGION
|
||||||
|
assert result[0].status == "FAIL"
|
||||||
|
assert (
|
||||||
|
result[0].status_extended
|
||||||
|
== f"Contact information is public for the {domain_name} domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_domain_privacy_protection_enabled(self):
|
||||||
|
route53domains = mock.MagicMock
|
||||||
|
domain_name = "test-domain.com"
|
||||||
|
route53domains.domains = {
|
||||||
|
domain_name: Domain(name=domain_name, region=AWS_REGION, admin_privacy=True)
|
||||||
|
}
|
||||||
|
|
||||||
|
with mock.patch(
|
||||||
|
"providers.aws.services.route53.route53_service.Route53Domains",
|
||||||
|
new=route53domains,
|
||||||
|
):
|
||||||
|
# Test Check
|
||||||
|
from providers.aws.services.route53.route53_domains_privacy_protection_enabled.route53_domains_privacy_protection_enabled import (
|
||||||
|
route53_domains_privacy_protection_enabled,
|
||||||
|
)
|
||||||
|
|
||||||
|
check = route53_domains_privacy_protection_enabled()
|
||||||
|
result = check.execute()
|
||||||
|
|
||||||
|
assert len(result) == 1
|
||||||
|
assert result[0].resource_id == domain_name
|
||||||
|
assert result[0].region == AWS_REGION
|
||||||
|
assert result[0].status == "PASS"
|
||||||
|
assert (
|
||||||
|
result[0].status_extended
|
||||||
|
== f"Contact information is private for the {domain_name} domain"
|
||||||
|
)
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"Provider": "aws",
|
||||||
|
"CheckID": "route53_domains_transferlock_enabled",
|
||||||
|
"CheckTitle": "Enable Transfer Lock for a Route53 Domain.",
|
||||||
|
"CheckType": [],
|
||||||
|
"ServiceName": "route53",
|
||||||
|
"SubServiceName": "",
|
||||||
|
"ResourceIdTemplate": "",
|
||||||
|
"Severity": "medium",
|
||||||
|
"ResourceType": "AwsRoute53Domain",
|
||||||
|
"Description": "Enable Transfer Lock for a Route53 Domain.",
|
||||||
|
"Risk": "Without transfer lock enabled; a domain name could be incorrectly moved to a new registrar.",
|
||||||
|
"RelatedUrl": "https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/domain-lock.html",
|
||||||
|
"Remediation": {
|
||||||
|
"Code": {
|
||||||
|
"CLI": "aws route53domains enable-domain-transfer-lock --domain-name DOMAIN",
|
||||||
|
"NativeIaC": "",
|
||||||
|
"Other": "",
|
||||||
|
"Terraform": ""
|
||||||
|
},
|
||||||
|
"Recommendation": {
|
||||||
|
"Text": "Ensure transfer lock is enabled.",
|
||||||
|
"Url": "https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/domain-lock.html"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Categories": [],
|
||||||
|
"Tags": {
|
||||||
|
"Tag1Key": "value",
|
||||||
|
"Tag2Key": "value"
|
||||||
|
},
|
||||||
|
"DependsOn": [],
|
||||||
|
"RelatedTo": [],
|
||||||
|
"Notes": "",
|
||||||
|
"Compliance": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
from lib.check.models import Check, Check_Report
|
||||||
|
from providers.aws.services.route53.route53domains_client import route53domains_client
|
||||||
|
|
||||||
|
|
||||||
|
class route53_domains_transferlock_enabled(Check):
|
||||||
|
def execute(self) -> Check_Report:
|
||||||
|
findings = []
|
||||||
|
|
||||||
|
for domain in route53domains_client.domains.values():
|
||||||
|
report = Check_Report(self.metadata)
|
||||||
|
report.resource_id = domain.name
|
||||||
|
report.region = domain.region
|
||||||
|
|
||||||
|
if "clientTransferProhibited" in domain.status_list:
|
||||||
|
report.status = "PASS"
|
||||||
|
report.status_extended = (
|
||||||
|
f"Transfer Lock is enabled for the {domain.name} domain"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
report.status = "FAIL"
|
||||||
|
report.status_extended = (
|
||||||
|
f"Transfer Lock is disabled for the {domain.name} domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
findings.append(report)
|
||||||
|
|
||||||
|
return findings
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
from providers.aws.services.route53.route53_service import Domain
|
||||||
|
|
||||||
|
AWS_REGION = "us-east-1"
|
||||||
|
|
||||||
|
|
||||||
|
class Test_route53_domains_transferlock_enabled:
|
||||||
|
def test_no_domains(self):
|
||||||
|
route53domains = mock.MagicMock
|
||||||
|
route53domains.domains = {}
|
||||||
|
|
||||||
|
with mock.patch(
|
||||||
|
"providers.aws.services.route53.route53_service.Route53Domains",
|
||||||
|
new=route53domains,
|
||||||
|
):
|
||||||
|
# Test Check
|
||||||
|
from providers.aws.services.route53.route53_domains_transferlock_enabled.route53_domains_transferlock_enabled import (
|
||||||
|
route53_domains_transferlock_enabled,
|
||||||
|
)
|
||||||
|
|
||||||
|
check = route53_domains_transferlock_enabled()
|
||||||
|
result = check.execute()
|
||||||
|
|
||||||
|
assert len(result) == 0
|
||||||
|
|
||||||
|
def test_domain_transfer_lock_disabled(self):
|
||||||
|
route53domains = mock.MagicMock
|
||||||
|
domain_name = "test-domain.com"
|
||||||
|
route53domains.domains = {
|
||||||
|
domain_name: Domain(
|
||||||
|
name=domain_name,
|
||||||
|
region=AWS_REGION,
|
||||||
|
admin_privacy=False,
|
||||||
|
status_list=[""],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
with mock.patch(
|
||||||
|
"providers.aws.services.route53.route53_service.Route53Domains",
|
||||||
|
new=route53domains,
|
||||||
|
):
|
||||||
|
# Test Check
|
||||||
|
from providers.aws.services.route53.route53_domains_transferlock_enabled.route53_domains_transferlock_enabled import (
|
||||||
|
route53_domains_transferlock_enabled,
|
||||||
|
)
|
||||||
|
|
||||||
|
check = route53_domains_transferlock_enabled()
|
||||||
|
result = check.execute()
|
||||||
|
|
||||||
|
assert len(result) == 1
|
||||||
|
assert result[0].resource_id == domain_name
|
||||||
|
assert result[0].region == AWS_REGION
|
||||||
|
assert result[0].status == "FAIL"
|
||||||
|
assert (
|
||||||
|
result[0].status_extended
|
||||||
|
== f"Transfer Lock is disabled for the {domain_name} domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_domain_transfer_lock_enabled(self):
|
||||||
|
route53domains = mock.MagicMock
|
||||||
|
domain_name = "test-domain.com"
|
||||||
|
route53domains.domains = {
|
||||||
|
domain_name: Domain(
|
||||||
|
name=domain_name,
|
||||||
|
region=AWS_REGION,
|
||||||
|
admin_privacy=False,
|
||||||
|
status_list=["clientTransferProhibited"],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
with mock.patch(
|
||||||
|
"providers.aws.services.route53.route53_service.Route53Domains",
|
||||||
|
new=route53domains,
|
||||||
|
):
|
||||||
|
# Test Check
|
||||||
|
from providers.aws.services.route53.route53_domains_transferlock_enabled.route53_domains_transferlock_enabled import (
|
||||||
|
route53_domains_transferlock_enabled,
|
||||||
|
)
|
||||||
|
|
||||||
|
check = route53_domains_transferlock_enabled()
|
||||||
|
result = check.execute()
|
||||||
|
|
||||||
|
assert len(result) == 1
|
||||||
|
assert result[0].resource_id == domain_name
|
||||||
|
assert result[0].region == AWS_REGION
|
||||||
|
assert result[0].status == "PASS"
|
||||||
|
assert (
|
||||||
|
result[0].status_extended
|
||||||
|
== f"Transfer Lock is enabled for the {domain_name} domain"
|
||||||
|
)
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"Provider": "aws",
|
||||||
|
"CheckID": "route53_public_hosted_zones_cloudwatch_logging_enabled",
|
||||||
|
"CheckTitle": "Check if Route53 public hosted zones are logging queries to CloudWatch Logs.",
|
||||||
|
"CheckType": [],
|
||||||
|
"ServiceName": "route53",
|
||||||
|
"SubServiceName": "",
|
||||||
|
"ResourceIdTemplate": "",
|
||||||
|
"Severity": "medium",
|
||||||
|
"ResourceType": "AwsRoute53HostedZone",
|
||||||
|
"Description": "Check if Route53 public hosted zones are logging queries to CloudWatch Logs.",
|
||||||
|
"Risk": "If logs are not enabled; monitoring of service use and threat analysis is not possible.",
|
||||||
|
"RelatedUrl": "https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/monitoring-hosted-zones-with-cloudwatch.html",
|
||||||
|
"Remediation": {
|
||||||
|
"Code": {
|
||||||
|
"CLI": "aws route53 create-query-logging-config --hosted-zone-id <zone_id> --cloud-watch-logs-log-group-arn <log_group_arn>",
|
||||||
|
"NativeIaC": "",
|
||||||
|
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/Route53/enable-query-logging.html",
|
||||||
|
"Terraform": ""
|
||||||
|
},
|
||||||
|
"Recommendation": {
|
||||||
|
"Text": "Enable CloudWatch logs and define metrics and uses cases for the events recorded.",
|
||||||
|
"Url": "https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/monitoring-hosted-zones-with-cloudwatch.html"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Categories": [],
|
||||||
|
"Tags": {
|
||||||
|
"Tag1Key": "value",
|
||||||
|
"Tag2Key": "value"
|
||||||
|
},
|
||||||
|
"DependsOn": [],
|
||||||
|
"RelatedTo": [],
|
||||||
|
"Notes": "",
|
||||||
|
"Compliance": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
from lib.check.models import Check, Check_Report
|
||||||
|
from providers.aws.services.route53.route53_client import route53_client
|
||||||
|
|
||||||
|
|
||||||
|
class route53_public_hosted_zones_cloudwatch_logging_enabled(Check):
|
||||||
|
def execute(self) -> Check_Report:
|
||||||
|
findings = []
|
||||||
|
|
||||||
|
for hosted_zone in route53_client.hosted_zones.values():
|
||||||
|
if not hosted_zone.private_zone:
|
||||||
|
report = Check_Report(self.metadata)
|
||||||
|
report.resource_id = hosted_zone.id
|
||||||
|
report.region = hosted_zone.region
|
||||||
|
if (
|
||||||
|
hosted_zone.logging_config
|
||||||
|
and hosted_zone.logging_config.cloudwatch_log_group_arn
|
||||||
|
):
|
||||||
|
report.status = "PASS"
|
||||||
|
report.status_extended = f"Route53 Public Hosted Zone {hosted_zone.id} has query logging enabled in Log Group {hosted_zone.logging_config.cloudwatch_log_group_arn}"
|
||||||
|
|
||||||
|
else:
|
||||||
|
report.status = "FAIL"
|
||||||
|
report.status_extended = f"Route53 Public Hosted Zone {hosted_zone.id} has query logging disabled"
|
||||||
|
|
||||||
|
findings.append(report)
|
||||||
|
|
||||||
|
return findings
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
from moto.core import DEFAULT_ACCOUNT_ID
|
||||||
|
|
||||||
|
from providers.aws.services.route53.route53_service import HostedZone, LoggingConfig
|
||||||
|
|
||||||
|
AWS_REGION = "us-east-1"
|
||||||
|
|
||||||
|
|
||||||
|
class Test_route53_public_hosted_zones_cloudwatch_logging_enabled:
|
||||||
|
def test_no_hosted_zones(self):
|
||||||
|
route53 = mock.MagicMock
|
||||||
|
route53.hosted_zones = {}
|
||||||
|
|
||||||
|
with mock.patch(
|
||||||
|
"providers.aws.services.route53.route53_service.Route53",
|
||||||
|
new=route53,
|
||||||
|
):
|
||||||
|
# Test Check
|
||||||
|
from providers.aws.services.route53.route53_public_hosted_zones_cloudwatch_logging_enabled.route53_public_hosted_zones_cloudwatch_logging_enabled import (
|
||||||
|
route53_public_hosted_zones_cloudwatch_logging_enabled,
|
||||||
|
)
|
||||||
|
|
||||||
|
check = route53_public_hosted_zones_cloudwatch_logging_enabled()
|
||||||
|
result = check.execute()
|
||||||
|
|
||||||
|
assert len(result) == 0
|
||||||
|
|
||||||
|
def test_hosted_zone__public_logging_enabled(self):
|
||||||
|
route53 = mock.MagicMock
|
||||||
|
hosted_zone_name = "test-domain.com"
|
||||||
|
hosted_zone_id = "ABCDEF12345678"
|
||||||
|
log_group_name = "test-log-group"
|
||||||
|
log_group_arn = (
|
||||||
|
f"rn:aws:logs:{AWS_REGION}:{DEFAULT_ACCOUNT_ID}:log-group:{log_group_name}"
|
||||||
|
)
|
||||||
|
route53.hosted_zones = {
|
||||||
|
hosted_zone_name: HostedZone(
|
||||||
|
name=hosted_zone_name,
|
||||||
|
id=hosted_zone_id,
|
||||||
|
private_zone=False,
|
||||||
|
region=AWS_REGION,
|
||||||
|
logging_config=LoggingConfig(cloudwatch_log_group_arn=log_group_arn),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
with mock.patch(
|
||||||
|
"providers.aws.services.route53.route53_service.Route53",
|
||||||
|
new=route53,
|
||||||
|
):
|
||||||
|
# Test Check
|
||||||
|
from providers.aws.services.route53.route53_public_hosted_zones_cloudwatch_logging_enabled.route53_public_hosted_zones_cloudwatch_logging_enabled import (
|
||||||
|
route53_public_hosted_zones_cloudwatch_logging_enabled,
|
||||||
|
)
|
||||||
|
|
||||||
|
check = route53_public_hosted_zones_cloudwatch_logging_enabled()
|
||||||
|
result = check.execute()
|
||||||
|
|
||||||
|
assert len(result) == 1
|
||||||
|
assert result[0].resource_id == hosted_zone_id
|
||||||
|
assert result[0].region == AWS_REGION
|
||||||
|
assert result[0].status == "PASS"
|
||||||
|
assert (
|
||||||
|
result[0].status_extended
|
||||||
|
== f"Route53 Public Hosted Zone {hosted_zone_id} has query logging enabled in Log Group {log_group_arn}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_hosted_zone__public_logging_disabled(self):
|
||||||
|
route53 = mock.MagicMock
|
||||||
|
hosted_zone_name = "test-domain.com"
|
||||||
|
hosted_zone_id = "ABCDEF12345678"
|
||||||
|
route53.hosted_zones = {
|
||||||
|
hosted_zone_name: HostedZone(
|
||||||
|
name=hosted_zone_name,
|
||||||
|
id=hosted_zone_id,
|
||||||
|
private_zone=False,
|
||||||
|
region=AWS_REGION,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
with mock.patch(
|
||||||
|
"providers.aws.services.route53.route53_service.Route53",
|
||||||
|
new=route53,
|
||||||
|
):
|
||||||
|
# Test Check
|
||||||
|
from providers.aws.services.route53.route53_public_hosted_zones_cloudwatch_logging_enabled.route53_public_hosted_zones_cloudwatch_logging_enabled import (
|
||||||
|
route53_public_hosted_zones_cloudwatch_logging_enabled,
|
||||||
|
)
|
||||||
|
|
||||||
|
check = route53_public_hosted_zones_cloudwatch_logging_enabled()
|
||||||
|
result = check.execute()
|
||||||
|
|
||||||
|
assert len(result) == 1
|
||||||
|
assert result[0].resource_id == hosted_zone_id
|
||||||
|
assert result[0].region == AWS_REGION
|
||||||
|
assert result[0].status == "FAIL"
|
||||||
|
assert (
|
||||||
|
result[0].status_extended
|
||||||
|
== f"Route53 Public Hosted Zone {hosted_zone_id} has query logging disabled"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_hosted_zone__private(self):
|
||||||
|
route53 = mock.MagicMock
|
||||||
|
hosted_zone_name = "test-domain.com"
|
||||||
|
hosted_zone_id = "ABCDEF12345678"
|
||||||
|
route53.hosted_zones = {
|
||||||
|
hosted_zone_name: HostedZone(
|
||||||
|
name=hosted_zone_name,
|
||||||
|
id=hosted_zone_id,
|
||||||
|
private_zone=True,
|
||||||
|
region=AWS_REGION,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
with mock.patch(
|
||||||
|
"providers.aws.services.route53.route53_service.Route53",
|
||||||
|
new=route53,
|
||||||
|
):
|
||||||
|
# Test Check
|
||||||
|
from providers.aws.services.route53.route53_public_hosted_zones_cloudwatch_logging_enabled.route53_public_hosted_zones_cloudwatch_logging_enabled import (
|
||||||
|
route53_public_hosted_zones_cloudwatch_logging_enabled,
|
||||||
|
)
|
||||||
|
|
||||||
|
check = route53_public_hosted_zones_cloudwatch_logging_enabled()
|
||||||
|
result = check.execute()
|
||||||
|
|
||||||
|
assert len(result) == 0
|
||||||
128
providers/aws/services/route53/route53_service.py
Normal file
128
providers/aws/services/route53/route53_service.py
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from lib.logger import logger
|
||||||
|
from providers.aws.aws_provider import get_region_global_service
|
||||||
|
|
||||||
|
|
||||||
|
################## Route53
|
||||||
|
class Route53:
|
||||||
|
def __init__(self, audit_info):
|
||||||
|
self.service = "route53"
|
||||||
|
self.session = audit_info.audit_session
|
||||||
|
self.client = self.session.client(self.service)
|
||||||
|
self.region = get_region_global_service(audit_info)
|
||||||
|
self.hosted_zones = {}
|
||||||
|
self.__list_hosted_zones__()
|
||||||
|
self.__list_query_logging_configs__()
|
||||||
|
|
||||||
|
def __get_session__(self):
|
||||||
|
return self.session
|
||||||
|
|
||||||
|
def __list_hosted_zones__(self):
|
||||||
|
logger.info("Route53 - Listing Hosting Zones...")
|
||||||
|
try:
|
||||||
|
list_hosted_zones_paginator = self.client.get_paginator("list_hosted_zones")
|
||||||
|
for page in list_hosted_zones_paginator.paginate():
|
||||||
|
for hosted_zone in page["HostedZones"]:
|
||||||
|
hosted_zone_id = hosted_zone["Id"].replace("/hostedzone/", "")
|
||||||
|
hosted_zone_name = hosted_zone["Name"]
|
||||||
|
private_zone = hosted_zone["Config"]["PrivateZone"]
|
||||||
|
|
||||||
|
self.hosted_zones[hosted_zone_id] = HostedZone(
|
||||||
|
id=hosted_zone_id,
|
||||||
|
name=hosted_zone_name,
|
||||||
|
private_zone=private_zone,
|
||||||
|
region=self.region,
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as error:
|
||||||
|
logger.error(
|
||||||
|
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __list_query_logging_configs__(self):
|
||||||
|
logger.info("Route53 - Listing Query Logging Configs...")
|
||||||
|
try:
|
||||||
|
for hosted_zone in self.hosted_zones.values():
|
||||||
|
list_query_logging_configs_paginator = self.client.get_paginator(
|
||||||
|
"list_query_logging_configs"
|
||||||
|
)
|
||||||
|
for page in list_query_logging_configs_paginator.paginate():
|
||||||
|
for logging_config in page["QueryLoggingConfigs"]:
|
||||||
|
self.hosted_zones[
|
||||||
|
hosted_zone.id
|
||||||
|
].logging_config = LoggingConfig(
|
||||||
|
cloudwatch_log_group_arn=logging_config[
|
||||||
|
"CloudWatchLogsLogGroupArn"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as error:
|
||||||
|
logger.error(
|
||||||
|
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class LoggingConfig(BaseModel):
|
||||||
|
cloudwatch_log_group_arn: str
|
||||||
|
|
||||||
|
|
||||||
|
class HostedZone(BaseModel):
|
||||||
|
id: str
|
||||||
|
name: str
|
||||||
|
private_zone: bool
|
||||||
|
logging_config: LoggingConfig = None
|
||||||
|
region: str
|
||||||
|
|
||||||
|
|
||||||
|
################## Route53Domains
|
||||||
|
class Route53Domains:
|
||||||
|
def __init__(self, audit_info):
|
||||||
|
self.service = "route53domains"
|
||||||
|
self.session = audit_info.audit_session
|
||||||
|
self.audited_account = audit_info.audited_account
|
||||||
|
self.region = get_region_global_service(audit_info)
|
||||||
|
self.client = self.session.client(self.service, self.region)
|
||||||
|
self.domains = {}
|
||||||
|
self.__list_domains__()
|
||||||
|
self.__get_domain_detail__()
|
||||||
|
|
||||||
|
def __get_session__(self):
|
||||||
|
return self.session
|
||||||
|
|
||||||
|
def __list_domains__(self):
|
||||||
|
logger.info("Route53Domains - Listing Domains...")
|
||||||
|
try:
|
||||||
|
list_domains_zones_paginator = self.client.get_paginator("list_domains")
|
||||||
|
for page in list_domains_zones_paginator.paginate():
|
||||||
|
for domain in page["Domains"]:
|
||||||
|
domain_name = domain["DomainName"]
|
||||||
|
|
||||||
|
self.domains[domain_name] = Domain(
|
||||||
|
name=domain_name, region=self.region
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as error:
|
||||||
|
logger.error(
|
||||||
|
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __get_domain_detail__(self):
|
||||||
|
logger.info("Route53Domains - Getting Domain Detail...")
|
||||||
|
try:
|
||||||
|
for domain in self.domains.values():
|
||||||
|
domain_detail = self.client.get_domain_detail(DomainName=domain.name)
|
||||||
|
self.domains[domain.name].admin_privacy = domain_detail["AdminPrivacy"]
|
||||||
|
self.domains[domain.name].status_list = domain_detail["StatusList"]
|
||||||
|
|
||||||
|
except Exception as error:
|
||||||
|
logger.error(
|
||||||
|
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Domain(BaseModel):
|
||||||
|
name: str
|
||||||
|
region: str
|
||||||
|
admin_privacy: bool = False
|
||||||
|
status_list: list[str] = None
|
||||||
190
providers/aws/services/route53/route53_service_test.py
Normal file
190
providers/aws/services/route53/route53_service_test.py
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import botocore
|
||||||
|
from boto3 import client, session
|
||||||
|
from moto import mock_logs, mock_route53
|
||||||
|
|
||||||
|
from providers.aws.lib.audit_info.audit_info import AWS_Audit_Info
|
||||||
|
from providers.aws.services.route53.route53_service import Route53
|
||||||
|
|
||||||
|
# Mock Test Region
|
||||||
|
AWS_REGION = "us-east-1"
|
||||||
|
|
||||||
|
# Mocking Access Analyzer 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 == "DescribeDirectories":
|
||||||
|
return {}
|
||||||
|
|
||||||
|
return make_api_call(self, operation_name, kwarg)
|
||||||
|
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
class Test_Route53_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=None,
|
||||||
|
audited_user_id=None,
|
||||||
|
audited_partition="aws",
|
||||||
|
audited_identity_arn=None,
|
||||||
|
profile=None,
|
||||||
|
profile_region=AWS_REGION,
|
||||||
|
credentials=None,
|
||||||
|
assumed_role_info=None,
|
||||||
|
audited_regions=None,
|
||||||
|
organizations_metadata=None,
|
||||||
|
)
|
||||||
|
return audit_info
|
||||||
|
|
||||||
|
# Test Route53 Client
|
||||||
|
@mock_route53
|
||||||
|
def test__get_client__(self):
|
||||||
|
route53 = Route53(self.set_mocked_audit_info())
|
||||||
|
assert route53.client.__class__.__name__ == "Route53"
|
||||||
|
|
||||||
|
# Test Route53 Session
|
||||||
|
@mock_route53
|
||||||
|
def test__get_session__(self):
|
||||||
|
route53 = Route53(self.set_mocked_audit_info())
|
||||||
|
assert route53.session.__class__.__name__ == "Session"
|
||||||
|
|
||||||
|
# Test Route53 Service
|
||||||
|
@mock_route53
|
||||||
|
def test__get_service__(self):
|
||||||
|
route53 = Route53(self.set_mocked_audit_info())
|
||||||
|
assert route53.service == "route53"
|
||||||
|
|
||||||
|
@mock_route53
|
||||||
|
@mock_logs
|
||||||
|
def test__list_hosted_zones__private_with_logging(self):
|
||||||
|
# Create Hosted Zone
|
||||||
|
r53_client = client("route53", region_name=AWS_REGION)
|
||||||
|
hosted_zone_name = "testdns.aws.com."
|
||||||
|
response = r53_client.create_hosted_zone(
|
||||||
|
Name=hosted_zone_name,
|
||||||
|
CallerReference=str(hash("foo")),
|
||||||
|
HostedZoneConfig={"Comment": "", "PrivateZone": True},
|
||||||
|
)
|
||||||
|
hosted_zone_id = response["HostedZone"]["Id"].replace("/hostedzone/", "")
|
||||||
|
hosted_zone_name = response["HostedZone"]["Name"]
|
||||||
|
# CloudWatch Client
|
||||||
|
logs_client = client("logs", region_name=AWS_REGION)
|
||||||
|
log_group_name = "test-log-group"
|
||||||
|
_ = logs_client.create_log_group(logGroupName=log_group_name)
|
||||||
|
log_group_arn = logs_client.describe_log_groups()["logGroups"][0]["arn"]
|
||||||
|
|
||||||
|
# Create Query Logging Config
|
||||||
|
response = r53_client.create_query_logging_config(
|
||||||
|
HostedZoneId=hosted_zone_id, CloudWatchLogsLogGroupArn=log_group_arn
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set partition for the service
|
||||||
|
route53 = Route53(self.set_mocked_audit_info())
|
||||||
|
assert len(route53.hosted_zones) == 1
|
||||||
|
assert route53.hosted_zones[hosted_zone_id]
|
||||||
|
assert route53.hosted_zones[hosted_zone_id].id == hosted_zone_id
|
||||||
|
assert route53.hosted_zones[hosted_zone_id].name == hosted_zone_name
|
||||||
|
assert route53.hosted_zones[hosted_zone_id].private_zone
|
||||||
|
assert route53.hosted_zones[hosted_zone_id].logging_config
|
||||||
|
assert (
|
||||||
|
route53.hosted_zones[hosted_zone_id].logging_config.cloudwatch_log_group_arn
|
||||||
|
== log_group_arn
|
||||||
|
)
|
||||||
|
assert route53.hosted_zones[hosted_zone_id].region == AWS_REGION
|
||||||
|
|
||||||
|
@mock_route53
|
||||||
|
@mock_logs
|
||||||
|
def test__list_hosted_zones__public_with_logging(self):
|
||||||
|
# Create Hosted Zone
|
||||||
|
r53_client = client("route53", region_name=AWS_REGION)
|
||||||
|
hosted_zone_name = "testdns.aws.com."
|
||||||
|
response = r53_client.create_hosted_zone(
|
||||||
|
Name=hosted_zone_name,
|
||||||
|
CallerReference=str(hash("foo")),
|
||||||
|
HostedZoneConfig={"Comment": "", "PrivateZone": False},
|
||||||
|
)
|
||||||
|
hosted_zone_id = response["HostedZone"]["Id"].replace("/hostedzone/", "")
|
||||||
|
hosted_zone_name = response["HostedZone"]["Name"]
|
||||||
|
# CloudWatch Client
|
||||||
|
logs_client = client("logs", region_name=AWS_REGION)
|
||||||
|
log_group_name = "test-log-group"
|
||||||
|
_ = logs_client.create_log_group(logGroupName=log_group_name)
|
||||||
|
log_group_arn = logs_client.describe_log_groups()["logGroups"][0]["arn"]
|
||||||
|
|
||||||
|
# Create Query Logging Config
|
||||||
|
response = r53_client.create_query_logging_config(
|
||||||
|
HostedZoneId=hosted_zone_id, CloudWatchLogsLogGroupArn=log_group_arn
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set partition for the service
|
||||||
|
route53 = Route53(self.set_mocked_audit_info())
|
||||||
|
assert len(route53.hosted_zones) == 1
|
||||||
|
assert route53.hosted_zones[hosted_zone_id]
|
||||||
|
assert route53.hosted_zones[hosted_zone_id].id == hosted_zone_id
|
||||||
|
assert route53.hosted_zones[hosted_zone_id].name == hosted_zone_name
|
||||||
|
assert not route53.hosted_zones[hosted_zone_id].private_zone
|
||||||
|
assert route53.hosted_zones[hosted_zone_id].logging_config
|
||||||
|
assert (
|
||||||
|
route53.hosted_zones[hosted_zone_id].logging_config.cloudwatch_log_group_arn
|
||||||
|
== log_group_arn
|
||||||
|
)
|
||||||
|
assert route53.hosted_zones[hosted_zone_id].region == AWS_REGION
|
||||||
|
|
||||||
|
@mock_route53
|
||||||
|
@mock_logs
|
||||||
|
def test__list_hosted_zones__private_without_logging(self):
|
||||||
|
# Create Hosted Zone
|
||||||
|
r53_client = client("route53", region_name=AWS_REGION)
|
||||||
|
hosted_zone_name = "testdns.aws.com."
|
||||||
|
response = r53_client.create_hosted_zone(
|
||||||
|
Name=hosted_zone_name,
|
||||||
|
CallerReference=str(hash("foo")),
|
||||||
|
HostedZoneConfig={"Comment": "", "PrivateZone": True},
|
||||||
|
)
|
||||||
|
hosted_zone_id = response["HostedZone"]["Id"].replace("/hostedzone/", "")
|
||||||
|
hosted_zone_name = response["HostedZone"]["Name"]
|
||||||
|
|
||||||
|
# Set partition for the service
|
||||||
|
route53 = Route53(self.set_mocked_audit_info())
|
||||||
|
assert len(route53.hosted_zones) == 1
|
||||||
|
assert route53.hosted_zones[hosted_zone_id]
|
||||||
|
assert route53.hosted_zones[hosted_zone_id].id == hosted_zone_id
|
||||||
|
assert route53.hosted_zones[hosted_zone_id].name == hosted_zone_name
|
||||||
|
assert route53.hosted_zones[hosted_zone_id].private_zone
|
||||||
|
assert not route53.hosted_zones[hosted_zone_id].logging_config
|
||||||
|
assert route53.hosted_zones[hosted_zone_id].region == AWS_REGION
|
||||||
|
|
||||||
|
@mock_route53
|
||||||
|
@mock_logs
|
||||||
|
def test__list_hosted_zones__public_without_logging(self):
|
||||||
|
# Create Hosted Zone
|
||||||
|
r53_client = client("route53", region_name=AWS_REGION)
|
||||||
|
hosted_zone_name = "testdns.aws.com."
|
||||||
|
response = r53_client.create_hosted_zone(
|
||||||
|
Name=hosted_zone_name,
|
||||||
|
CallerReference=str(hash("foo")),
|
||||||
|
HostedZoneConfig={"Comment": "", "PrivateZone": False},
|
||||||
|
)
|
||||||
|
hosted_zone_id = response["HostedZone"]["Id"].replace("/hostedzone/", "")
|
||||||
|
hosted_zone_name = response["HostedZone"]["Name"]
|
||||||
|
|
||||||
|
# Set partition for the service
|
||||||
|
route53 = Route53(self.set_mocked_audit_info())
|
||||||
|
assert len(route53.hosted_zones) == 1
|
||||||
|
assert route53.hosted_zones[hosted_zone_id]
|
||||||
|
assert route53.hosted_zones[hosted_zone_id].id == hosted_zone_id
|
||||||
|
assert route53.hosted_zones[hosted_zone_id].name == hosted_zone_name
|
||||||
|
assert not route53.hosted_zones[hosted_zone_id].private_zone
|
||||||
|
assert not route53.hosted_zones[hosted_zone_id].logging_config
|
||||||
|
|
||||||
|
assert route53.hosted_zones[hosted_zone_id].region == AWS_REGION
|
||||||
4
providers/aws/services/route53/route53domains_client.py
Normal file
4
providers/aws/services/route53/route53domains_client.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
from providers.aws.lib.audit_info.audit_info import current_audit_info
|
||||||
|
from providers.aws.services.route53.route53_service import Route53Domains
|
||||||
|
|
||||||
|
route53domains_client = Route53Domains(current_audit_info)
|
||||||
117
providers/aws/services/route53/route53domains_service_test.py
Normal file
117
providers/aws/services/route53/route53domains_service_test.py
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import botocore
|
||||||
|
from boto3 import session
|
||||||
|
|
||||||
|
from providers.aws.lib.audit_info.audit_info import AWS_Audit_Info
|
||||||
|
from providers.aws.services.route53.route53_service import Route53Domains
|
||||||
|
|
||||||
|
# Mock Test Region
|
||||||
|
AWS_REGION = "us-east-1"
|
||||||
|
|
||||||
|
# Mocking Access Analyzer 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 == "ListDomains":
|
||||||
|
return {
|
||||||
|
"Domains": [
|
||||||
|
{
|
||||||
|
"DomainName": "test.domain.com",
|
||||||
|
"AutoRenew": True,
|
||||||
|
"TransferLock": True,
|
||||||
|
"Expiry": datetime(2015, 1, 1),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"NextPageMarker": "string",
|
||||||
|
}
|
||||||
|
if operation_name == "GetDomainDetail":
|
||||||
|
return {
|
||||||
|
"DomainName": "test.domain.com",
|
||||||
|
"Nameservers": [
|
||||||
|
{
|
||||||
|
"Name": "8.8.8.8",
|
||||||
|
"GlueIps": [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"AutoRenew": True,
|
||||||
|
"AdminContact": {},
|
||||||
|
"RegistrantContact": {},
|
||||||
|
"TechContact": {},
|
||||||
|
"AdminPrivacy": True,
|
||||||
|
"RegistrantPrivacy": True,
|
||||||
|
"TechPrivacy": True,
|
||||||
|
"RegistrarName": "string",
|
||||||
|
"WhoIsServer": "string",
|
||||||
|
"RegistrarUrl": "string",
|
||||||
|
"AbuseContactEmail": "string",
|
||||||
|
"AbuseContactPhone": "string",
|
||||||
|
"RegistryDomainId": "string",
|
||||||
|
"CreationDate": datetime(2015, 1, 1),
|
||||||
|
"UpdatedDate": datetime(2015, 1, 1),
|
||||||
|
"ExpirationDate": datetime(2015, 1, 1),
|
||||||
|
"Reseller": "string",
|
||||||
|
"DnsSec": "string",
|
||||||
|
"StatusList": ["clientTransferProhibited"],
|
||||||
|
}
|
||||||
|
|
||||||
|
return make_api_call(self, operation_name, kwarg)
|
||||||
|
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
class Test_Route53_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=None,
|
||||||
|
audited_user_id=None,
|
||||||
|
audited_partition="aws",
|
||||||
|
audited_identity_arn=None,
|
||||||
|
profile=None,
|
||||||
|
profile_region=AWS_REGION,
|
||||||
|
credentials=None,
|
||||||
|
assumed_role_info=None,
|
||||||
|
audited_regions=None,
|
||||||
|
organizations_metadata=None,
|
||||||
|
)
|
||||||
|
return audit_info
|
||||||
|
|
||||||
|
# Test Route53Domains Client
|
||||||
|
def test__get_client__(self):
|
||||||
|
route53domains = Route53Domains(self.set_mocked_audit_info())
|
||||||
|
assert route53domains.client.__class__.__name__ == "Route53Domains"
|
||||||
|
|
||||||
|
# Test Route53Domains Session
|
||||||
|
def test__get_session__(self):
|
||||||
|
route53domains = Route53Domains(self.set_mocked_audit_info())
|
||||||
|
assert route53domains.session.__class__.__name__ == "Session"
|
||||||
|
|
||||||
|
# Test Route53Domains Service
|
||||||
|
def test__get_service__(self):
|
||||||
|
route53domains = Route53Domains(self.set_mocked_audit_info())
|
||||||
|
assert route53domains.service == "route53domains"
|
||||||
|
|
||||||
|
def test__list_domains__(self):
|
||||||
|
route53domains = Route53Domains(self.set_mocked_audit_info())
|
||||||
|
domain_name = "test.domain.com"
|
||||||
|
assert len(route53domains.domains)
|
||||||
|
assert route53domains.domains
|
||||||
|
assert route53domains.domains[domain_name]
|
||||||
|
assert route53domains.domains[domain_name].name == domain_name
|
||||||
|
assert route53domains.domains[domain_name].region == AWS_REGION
|
||||||
|
assert route53domains.domains[domain_name].admin_privacy
|
||||||
|
assert route53domains.domains[domain_name].status_list
|
||||||
|
assert len(route53domains.domains[domain_name].status_list) == 1
|
||||||
|
assert (
|
||||||
|
"clientTransferProhibited"
|
||||||
|
in route53domains.domains[domain_name].status_list
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user