mirror of
https://github.com/ghndrx/prowler.git
synced 2026-02-10 14:55:00 +00:00
feat(RDS): Service and missing checks (#1513)
This commit is contained in:
0
providers/aws/services/rds/__init__.py
Normal file
0
providers/aws/services/rds/__init__.py
Normal file
@@ -1,58 +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.
|
||||
|
||||
# Remediation:
|
||||
#
|
||||
# https://www.cloudconformity.com/knowledge-base/aws/RDS/instance-deletion-protection.html
|
||||
# https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Overview.DBInstance.Modifying.html
|
||||
#
|
||||
# aws rds modify-db-instance \
|
||||
# --region us-east-1 \
|
||||
# --db-instance-identifier test-db \
|
||||
# --deletion-protection \
|
||||
# [--apply-immediately | --no-apply-immediately]
|
||||
|
||||
CHECK_ID_extra7113="7.113"
|
||||
CHECK_TITLE_extra7113="[extra7113] Check if RDS instances have deletion protection enabled "
|
||||
CHECK_SCORED_extra7113="NOT_SCORED"
|
||||
CHECK_CIS_LEVEL_extra7113="EXTRA"
|
||||
CHECK_SEVERITY_extra7113="Medium"
|
||||
CHECK_ASFF_RESOURCE_TYPE_extra7113="AwsRdsDbInstance"
|
||||
CHECK_ALTERNATE_check7113="extra7113"
|
||||
CHECK_SERVICENAME_extra7113="rds"
|
||||
CHECK_RISK_extra7113='You can only delete instances that do not have deletion protection enabled.'
|
||||
CHECK_REMEDIATION_extra7113='Enable deletion protection using the AWS Management Console for production DB instances.'
|
||||
CHECK_DOC_extra7113='https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_DeleteInstance.html'
|
||||
CHECK_CAF_EPIC_extra7113='Data Protection'
|
||||
|
||||
extra7113(){
|
||||
for regx in $REGIONS; do
|
||||
LIST_OF_RDS_INSTANCES=$($AWSCLI rds describe-db-instances $PROFILE_OPT --region $regx --query "DBInstances[?Engine != 'docdb'].DBInstanceIdentifier" --output text 2>&1)
|
||||
if [[ $(echo "$LIST_OF_RDS_INSTANCES" | grep -E 'AccessDenied|UnauthorizedOperation|AuthorizationError') ]]; then
|
||||
textInfo "$regx: Access Denied trying to describe DB instances" "$regx"
|
||||
continue
|
||||
fi
|
||||
if [[ $LIST_OF_RDS_INSTANCES ]];then
|
||||
for rdsinstance in $LIST_OF_RDS_INSTANCES; do
|
||||
IS_DELETIONPROTECTION=$($AWSCLI rds describe-db-instances $PROFILE_OPT --region $regx --db-instance-identifier $rdsinstance --query 'DBInstances[*].DeletionProtection' --output text)
|
||||
if [[ $IS_DELETIONPROTECTION == "False" ]]; then
|
||||
textFail "$regx: RDS instance $rdsinstance deletion protection is not enabled!" "$regx" "$rdsinstance"
|
||||
else
|
||||
textPass "$regx: RDS instance $rdsinstance deletion protection is enabled" "$regx" "$rdsinstance"
|
||||
fi
|
||||
done
|
||||
else
|
||||
textInfo "$regx: No RDS instances found" "$regx"
|
||||
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_extra7131="7.131"
|
||||
CHECK_TITLE_extra7131="[extra7131] Ensure RDS instances have minor version upgrade enabled"
|
||||
CHECK_SCORED_extra7131="NOT_SCORED"
|
||||
CHECK_CIS_LEVEL_extra7131="EXTRA"
|
||||
CHECK_SEVERITY_extra7131="Low"
|
||||
CHECK_ASFF_RESOURCE_TYPE_extra7131="AwsRdsDbInstance"
|
||||
CHECK_ALTERNATE_check7131="extra7131"
|
||||
CHECK_SERVICENAME_extra7131="rds"
|
||||
CHECK_RISK_extra7131='Auto Minor Version Upgrade is a feature that you can enable to have your database automatically upgraded when a new minor database engine version is available. Minor version upgrades often patch security vulnerabilities and fix bugs; and therefor should be applied.'
|
||||
CHECK_REMEDIATION_extra7131='Enable auto minor version upgrade for all databases and environments.'
|
||||
CHECK_DOC_extra7131='https://aws.amazon.com/blogs/database/best-practices-for-upgrading-amazon-rds-to-major-and-minor-versions-of-postgresql/'
|
||||
CHECK_CAF_EPIC_extra7131='Infrastructure Security'
|
||||
|
||||
extra7131(){
|
||||
for regx in $REGIONS; do
|
||||
# LIST_OF_RDS_PUBLIC_INSTANCES=$($AWSCLI rds describe-db-instances $PROFILE_OPT --region $regx --query 'DBInstances[?PubliclyAccessible==`true` && DBInstanceStatus==`"available"`].[DBInstanceIdentifier,Endpoint.Address]' --output text)
|
||||
LIST_OF_RDS_INSTANCES=$($AWSCLI rds describe-db-instances $PROFILE_OPT --region $regx --query 'DBInstances[*].[DBInstanceIdentifier,AutoMinorVersionUpgrade]' --output text 2>&1)
|
||||
if [[ $(echo "$LIST_OF_RDS_INSTANCES" | grep -E 'AccessDenied|UnauthorizedOperation|AuthorizationError') ]]; then
|
||||
textInfo "$regx: Access Denied trying to describe DB instances" "$regx"
|
||||
continue
|
||||
fi
|
||||
if [[ $LIST_OF_RDS_INSTANCES ]];then
|
||||
while read -r rds_instance;do
|
||||
RDS_NAME=$(echo $rds_instance | awk '{ print $1; }')
|
||||
RDS_AUTOMINORUPGRADE_FLAG=$(echo $rds_instance | awk '{ print $2; }')
|
||||
if [[ $RDS_AUTOMINORUPGRADE_FLAG == "True" ]];then
|
||||
textPass "$regx: RDS instance: $RDS_NAME is has minor version upgrade enabled" "$regx" "$RDS_NAME"
|
||||
else
|
||||
textFail "$regx: RDS instance: $RDS_NAME does not have minor version upgrade enabled" "$regx" "$RDS_NAME"
|
||||
fi
|
||||
done <<< "$LIST_OF_RDS_INSTANCES"
|
||||
else
|
||||
textInfo "$regx: no RDS instances found" "$regx" "$RDS_NAME"
|
||||
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_extra7132="7.132"
|
||||
CHECK_TITLE_extra7132="[extra7132] Check if RDS instances has enhanced monitoring enabled"
|
||||
CHECK_SCORED_extra7132="NOT_SCORED"
|
||||
CHECK_CIS_LEVEL_extra7132="EXTRA"
|
||||
CHECK_SEVERITY_extra7132="Low"
|
||||
CHECK_ASFF_RESOURCE_TYPE_extra7132="AwsRdsDbInstance"
|
||||
CHECK_ALTERNATE_check7132="extra7132"
|
||||
CHECK_SERVICENAME_extra7132="rds"
|
||||
CHECK_RISK_extra7132='A smaller monitoring interval results in more frequent reporting of OS metrics.'
|
||||
CHECK_REMEDIATION_extra7132='To use Enhanced Monitoring; you must create an IAM role; and then enable Enhanced Monitoring.'
|
||||
CHECK_DOC_extra7132='https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_Monitoring.OS.html'
|
||||
CHECK_CAF_EPIC_extra7132='Logging and Monitoring'
|
||||
|
||||
extra7132(){
|
||||
for regx in $REGIONS; do
|
||||
RDS_INSTANCES=$($AWSCLI rds describe-db-instances $PROFILE_OPT --region $regx --query "DBInstances[?Engine != 'docdb'].DBInstanceIdentifier" --output text 2>&1)
|
||||
if [[ $(echo "$RDS_INSTANCES" | grep -E 'AccessDenied|UnauthorizedOperation|AuthorizationError') ]]; then
|
||||
textInfo "$regx: Access Denied trying to describe DB instances" "$regx"
|
||||
continue
|
||||
fi
|
||||
if [[ $RDS_INSTANCES ]];then
|
||||
for rdsinstance in ${RDS_INSTANCES}; do
|
||||
RDS_NAME="$rdsinstance"
|
||||
MONITORING_FLAG=$($AWSCLI rds describe-db-instances $PROFILE_OPT --region $regx --db-instance-identifier $rdsinstance --query 'DBInstances[*].[EnhancedMonitoringResourceArn]' --output text)
|
||||
if [[ $MONITORING_FLAG == "None" ]];then
|
||||
textFail "$regx: RDS instance: $RDS_NAME has enhanced monitoring disabled!" "$rex" "$RDS_NAME"
|
||||
else
|
||||
textPass "$regx: RDS instance: $RDS_NAME has enhanced monitoring enabled." "$regx" "$RDS_NAME"
|
||||
fi
|
||||
done
|
||||
else
|
||||
textInfo "$regx: no RDS instances found" "$regx" "$RDS_NAME"
|
||||
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_extra7133="7.133"
|
||||
CHECK_TITLE_extra7133="[extra7133] Check if RDS instances have multi-AZ enabled"
|
||||
CHECK_SCORED_extra7133="NOT_SCORED"
|
||||
CHECK_CIS_LEVEL_extra7133="EXTRA"
|
||||
CHECK_SEVERITY_extra7133="Medium"
|
||||
CHECK_ASFF_RESOURCE_TYPE_extra7133="AwsRdsDbInstance"
|
||||
CHECK_ALTERNATE_check7133="extra7133"
|
||||
CHECK_SERVICENAME_extra7133="rds"
|
||||
CHECK_RISK_extra7133='In case of failure; with a single-AZ deployment configuration; should an availability zone specific database failure occur; Amazon RDS can not automatically fail over to the standby availability zone.'
|
||||
CHECK_REMEDIATION_extra7133='Enable multi-AZ deployment for production databases.'
|
||||
CHECK_DOC_extra7133='https://aws.amazon.com/rds/features/multi-az/'
|
||||
CHECK_CAF_EPIC_extra7133='Data Protection'
|
||||
|
||||
extra7133(){
|
||||
for regx in $REGIONS; do
|
||||
RDS_INSTANCES=$($AWSCLI rds describe-db-instances $PROFILE_OPT --region $regx --query "DBInstances[?Engine != 'docdb'].DBInstanceIdentifier" --output text 2>&1)
|
||||
if [[ $(echo "$RDS_INSTANCES" | grep -E 'AccessDenied|UnauthorizedOperation|AuthorizationError') ]]; then
|
||||
textInfo "$regx: Access Denied trying to describe DB instances" "$regx"
|
||||
continue
|
||||
fi
|
||||
if [[ $RDS_INSTANCES ]];then
|
||||
for rdsinstance in ${RDS_INSTANCES}; do
|
||||
RDS_NAME="$rdsinstance"
|
||||
MULTIAZ_FLAG=$($AWSCLI rds describe-db-instances $PROFILE_OPT --region $regx --db-instance-identifier $rdsinstance --query 'DBInstances[*].MultiAZ' --output text)
|
||||
if [[ $MULTIAZ_FLAG == "True" ]];then
|
||||
textPass "$regx: RDS instance: $RDS_NAME has multi-AZ enabled" "$regx" "$RDS_NAME"
|
||||
else
|
||||
textFail "$regx: RDS instance: $RDS_NAME has multi-AZ disabled!" "$regx" "$RDS_NAME"
|
||||
fi
|
||||
done
|
||||
else
|
||||
textInfo "$regx: no RDS instances found" "$regx" "$RDS_NAME"
|
||||
fi
|
||||
done
|
||||
}
|
||||
@@ -1,62 +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_extra723="7.23"
|
||||
CHECK_TITLE_extra723="[extra723] Check if RDS Snapshots and Cluster Snapshots are public"
|
||||
CHECK_SCORED_extra723="NOT_SCORED"
|
||||
CHECK_CIS_LEVEL_extra723="EXTRA"
|
||||
CHECK_SEVERITY_extra723="Critical"
|
||||
CHECK_ASFF_RESOURCE_TYPE_extra723="AwsRdsDbSnapshot"
|
||||
CHECK_ALTERNATE_check723="extra723"
|
||||
CHECK_SERVICENAME_extra723="rds"
|
||||
CHECK_RISK_extra723='Publicly accessible services could expose sensitive data to bad actors. t is recommended that your RDS snapshots should not be public in order to prevent potential leak or misuse of sensitive data or any other kind of security threat. If your RDS snapshot is public; then the data which is backed up in that snapshot is accessible to all other AWS accounts.'
|
||||
CHECK_REMEDIATION_extra723='Use AWS Config to identify any sanpshot that is public.'
|
||||
CHECK_DOC_extra723='https://docs.aws.amazon.com/config/latest/developerguide/rds-snapshots-public-prohibited.html'
|
||||
CHECK_CAF_EPIC_extra723='Data Protection'
|
||||
|
||||
extra723(){
|
||||
# "Check if RDS Snapshots are public "
|
||||
for regx in $REGIONS; do
|
||||
# RDS snapshots
|
||||
LIST_OF_RDS_SNAPSHOTS=$($AWSCLI rds describe-db-snapshots $PROFILE_OPT --region $regx --query DBSnapshots[*].DBSnapshotIdentifier --output text 2>&1)
|
||||
if [[ $(echo "$LIST_OF_RDS_SNAPSHOTS" | grep -E 'AccessDenied|UnauthorizedOperation') ]]; then
|
||||
textInfo "$regx: Access Denied trying to describe db snapshots" "$regx"
|
||||
continue
|
||||
fi
|
||||
if [[ $LIST_OF_RDS_SNAPSHOTS ]]; then
|
||||
for rdssnapshot in $LIST_OF_RDS_SNAPSHOTS;do
|
||||
SNAPSHOT_IS_PUBLIC=$($AWSCLI rds describe-db-snapshot-attributes $PROFILE_OPT --region $regx --db-snapshot-identifier $rdssnapshot --query DBSnapshotAttributesResult.DBSnapshotAttributes[*] --output text|grep ^ATTRIBUTEVALUES|cut -f2|grep all)
|
||||
if [[ $SNAPSHOT_IS_PUBLIC ]];then
|
||||
textFail "$regx: RDS Snapshot $rdssnapshot is public!" "$regx" "$rdssnapshot"
|
||||
else
|
||||
textPass "$regx: RDS Snapshot $rdssnapshot is not shared" "$regx" "$rdssnapshot"
|
||||
fi
|
||||
done
|
||||
else
|
||||
textInfo "$regx: No RDS Snapshots found" "$regx" "$rdssnapshot"
|
||||
fi
|
||||
# RDS cluster snapshots
|
||||
LIST_OF_RDS_CLUSTER_SNAPSHOTS=$($AWSCLI rds describe-db-cluster-snapshots $PROFILE_OPT --region $regx --query DBClusterSnapshots[*].DBClusterSnapshotIdentifier --output text)
|
||||
if [[ $LIST_OF_RDS_CLUSTER_SNAPSHOTS ]]; then
|
||||
for rdsclustersnapshot in $LIST_OF_RDS_CLUSTER_SNAPSHOTS;do
|
||||
CLUSTER_SNAPSHOT_IS_PUBLIC=$($AWSCLI rds describe-db-cluster-snapshot-attributes $PROFILE_OPT --region $regx --db-cluster-snapshot-identifier $rdsclustersnapshot --query DBClusterSnapshotAttributesResult.DBClusterSnapshotAttributes[*] --output text|grep ^ATTRIBUTEVALUES|cut -f2|grep all)
|
||||
if [[ $CLUSTER_SNAPSHOT_IS_PUBLIC ]];then
|
||||
textFail "$regx: RDS Cluster Snapshot $rdsclustersnapshot is public!" "$regx" "$rdsclustersnapshot"
|
||||
else
|
||||
textPass "$regx: RDS Cluster Snapshot $rdsclustersnapshot is not shared" "$regx" "$rdsclustersnapshot"
|
||||
fi
|
||||
done
|
||||
else
|
||||
textInfo "$regx: No RDS Cluster Snapshots found" "$regx" "$rdsclustersnapshot"
|
||||
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_extra735="7.35"
|
||||
CHECK_TITLE_extra735="[extra735] Check if RDS instances storage is encrypted"
|
||||
CHECK_SCORED_extra735="NOT_SCORED"
|
||||
CHECK_CIS_LEVEL_extra735="EXTRA"
|
||||
CHECK_SEVERITY_extra735="Medium"
|
||||
CHECK_ASFF_RESOURCE_TYPE_extra735="AwsRdsDbInstance"
|
||||
CHECK_ALTERNATE_check735="extra735"
|
||||
CHECK_ASFF_COMPLIANCE_TYPE_extra735="ens-mp.info.3.aws.rds.1"
|
||||
CHECK_SERVICENAME_extra735="rds"
|
||||
CHECK_RISK_extra735='If not enabled sensitive information at rest is not protected.'
|
||||
CHECK_REMEDIATION_extra735='Enable Encryption. Use a CMK where possible. It will provide additional management and privacy benefits.'
|
||||
CHECK_DOC_extra735='https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Overview.Encryption.html'
|
||||
CHECK_CAF_EPIC_extra735='Data Protection'
|
||||
|
||||
extra735(){
|
||||
for regx in $REGIONS; do
|
||||
LIST_OF_RDS_INSTANCES=$($AWSCLI rds describe-db-instances $PROFILE_OPT --region $regx --query 'DBInstances[*].DBInstanceIdentifier' --output text 2>&1)
|
||||
if [[ $(echo "$LIST_OF_RDS_INSTANCES" | grep -E 'AccessDenied|UnauthorizedOperation|AuthorizationError') ]]; then
|
||||
textInfo "$regx: Access Denied trying to describe DB instances" "$regx"
|
||||
continue
|
||||
fi
|
||||
if [[ $LIST_OF_RDS_INSTANCES ]];then
|
||||
for rdsinstance in $LIST_OF_RDS_INSTANCES; do
|
||||
IS_ENCRYPTED=$($AWSCLI rds describe-db-instances $PROFILE_OPT --region $regx --db-instance-identifier $rdsinstance --query 'DBInstances[*].StorageEncrypted' --output text)
|
||||
if [[ $IS_ENCRYPTED == "False" ]]; then
|
||||
textFail "$regx: RDS instance $rdsinstance is not encrypted!" "$regx" "$rdsinstance"
|
||||
else
|
||||
textPass "$regx: RDS instance $rdsinstance is encrypted" "$regx" "$rdsinstance"
|
||||
fi
|
||||
done
|
||||
else
|
||||
textInfo "$regx: No RDS instances found" "$regx" "$rdsinstance"
|
||||
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_extra739="7.39"
|
||||
CHECK_TITLE_extra739="[extra739] Check if RDS instances have backup enabled"
|
||||
CHECK_SCORED_extra739="NOT_SCORED"
|
||||
CHECK_CIS_LEVEL_extra739="EXTRA"
|
||||
CHECK_SEVERITY_extra739="Medium"
|
||||
CHECK_ASFF_RESOURCE_TYPE_extra739="AwsRdsDbInstance"
|
||||
CHECK_ALTERNATE_check739="extra739"
|
||||
CHECK_SERVICENAME_extra739="rds"
|
||||
CHECK_RISK_extra739='If backup is not enabled; data is vulnerable. Human error or bad actors could erase or modify data.'
|
||||
CHECK_REMEDIATION_extra739='Enable automated backup for production data. Define a retention period and periodically test backup restoration. A Disaster Recovery process should be in place to govern Data Protection approach.'
|
||||
CHECK_DOC_extra739='https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_WorkingWithAutomatedBackups.html'
|
||||
CHECK_CAF_EPIC_extra739='Data Protection'
|
||||
|
||||
extra739(){
|
||||
for regx in $REGIONS; do
|
||||
LIST_OF_RDS_INSTANCES=$($AWSCLI rds describe-db-instances $PROFILE_OPT --region $regx --query 'DBInstances[*].DBInstanceIdentifier' --output text 2>&1)
|
||||
if [[ $(echo "$LIST_OF_RDS_INSTANCES" | grep -E 'AccessDenied|UnauthorizedOperation|AuthorizationError') ]]; then
|
||||
textInfo "$regx: Access Denied trying to describe DB instances" "$regx"
|
||||
continue
|
||||
fi
|
||||
if [[ $LIST_OF_RDS_INSTANCES ]];then
|
||||
for rdsinstance in $LIST_OF_RDS_INSTANCES; do
|
||||
# if retention is 0 then is disabled
|
||||
BACKUP_RETENTION=$($AWSCLI rds describe-db-instances $PROFILE_OPT --region $regx --db-instance-identifier $rdsinstance --query 'DBInstances[*].BackupRetentionPeriod' --output text)
|
||||
if [[ $BACKUP_RETENTION == "0" ]]; then
|
||||
textFail "$regx: RDS instance $rdsinstance has not backup enabled!" "$regx" "$rdsinstance"
|
||||
else
|
||||
textPass "$regx: RDS instance $rdsinstance has backup enabled with retention period $BACKUP_RETENTION days" "$regx" "$rdsinstance"
|
||||
fi
|
||||
done
|
||||
else
|
||||
textInfo "$regx: No RDS instances found" "$regx" "$rdsinstance"
|
||||
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_extra747="7.47"
|
||||
CHECK_TITLE_extra747="[extra747] Check if RDS instances is integrated with CloudWatch Logs"
|
||||
CHECK_SCORED_extra747="NOT_SCORED"
|
||||
CHECK_CIS_LEVEL_extra747="EXTRA"
|
||||
CHECK_SEVERITY_extra747="Medium"
|
||||
CHECK_ASFF_RESOURCE_TYPE_extra747="AwsRdsDbInstance"
|
||||
CHECK_ALTERNATE_check747="extra747"
|
||||
CHECK_SERVICENAME_extra747="rds"
|
||||
CHECK_RISK_extra747='If logs are not enabled; monitoring of service use and threat analysis is not possible.'
|
||||
CHECK_REMEDIATION_extra747='Use CloudWatch Logs to perform real-time analysis of the log data. Create alarms and view metrics.'
|
||||
CHECK_DOC_extra747='https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/publishing_cloudwatchlogs.html'
|
||||
CHECK_CAF_EPIC_extra747='Logging and Monitoring'
|
||||
|
||||
extra747(){
|
||||
for regx in $REGIONS; do
|
||||
LIST_OF_RDS_INSTANCES=$($AWSCLI rds describe-db-instances $PROFILE_OPT --region $regx --query 'DBInstances[*].DBInstanceIdentifier' --output text 2>&1)
|
||||
if [[ $(echo "$LIST_OF_RDS_INSTANCES" | grep -E 'AccessDenied|UnauthorizedOperation|AuthorizationError') ]]; then
|
||||
textInfo "$regx: Access Denied trying to get rest APIs" "$regx"
|
||||
continue
|
||||
fi
|
||||
if [[ $LIST_OF_RDS_INSTANCES ]];then
|
||||
for rdsinstance in $LIST_OF_RDS_INSTANCES; do
|
||||
# if retention is 0 then is disabled
|
||||
ENABLED_CLOUDWATCHLOGS_EXPORTS=$($AWSCLI rds describe-db-instances $PROFILE_OPT --region $regx --db-instance-identifier $rdsinstance --query 'DBInstances[*].EnabledCloudwatchLogsExports' --output text)
|
||||
if [[ $ENABLED_CLOUDWATCHLOGS_EXPORTS ]]; then
|
||||
textPass "$regx: RDS instance $rdsinstance is shipping $ENABLED_CLOUDWATCHLOGS_EXPORTS to CloudWatch Logs" "$regx" "$rdsinstance"
|
||||
else
|
||||
textFail "$regx: RDS instance $rdsinstance has no CloudWatch Logs enabled!" "$regx" "$rdsinstance"
|
||||
fi
|
||||
done
|
||||
else
|
||||
textInfo "$regx: No RDS instances found" "$regx" "$rdsinstance"
|
||||
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_extra78="7.8"
|
||||
CHECK_TITLE_extra78="[extra78] Ensure there are no Public Accessible RDS instances"
|
||||
CHECK_SCORED_extra78="NOT_SCORED"
|
||||
CHECK_CIS_LEVEL_extra78="EXTRA"
|
||||
CHECK_SEVERITY_extra78="Critical"
|
||||
CHECK_ASFF_RESOURCE_TYPE_extra78="AwsRdsDbInstance"
|
||||
CHECK_ALTERNATE_extra708="extra78"
|
||||
CHECK_ALTERNATE_check78="extra78"
|
||||
CHECK_ALTERNATE_check708="extra78"
|
||||
CHECK_SERVICENAME_extra78="rds"
|
||||
CHECK_RISK_extra78='Publicly accessible databases could expose sensitive data to bad actors.'
|
||||
CHECK_REMEDIATION_extra78='Using an AWS Config rule check for RDS public instances periodically and check there is a business reason for it.'
|
||||
CHECK_DOC_extra78='https://docs.amazonaws.cn/en_us/config/latest/developerguide/rds-instance-public-access-check.html'
|
||||
CHECK_CAF_EPIC_extra78='Data Protection'
|
||||
|
||||
extra78(){
|
||||
# "Ensure there are no Public Accessible RDS instances "
|
||||
for regx in $REGIONS; do
|
||||
LIST_OF_RDS_PUBLIC_INSTANCES=$($AWSCLI rds describe-db-instances $PROFILE_OPT --region $regx --query 'DBInstances[?PubliclyAccessible==`true` && DBInstanceStatus==`"available"`].[DBInstanceIdentifier,Endpoint.Address]' --output text 2>&1)
|
||||
if [[ $(echo "$LIST_OF_RDS_PUBLIC_INSTANCES" | grep AccessDenied) ]]; then
|
||||
textInfo "$regx: Access Denied Trying to describe DB instances" "$regx"
|
||||
continue
|
||||
fi
|
||||
if [[ $LIST_OF_RDS_PUBLIC_INSTANCES ]];then
|
||||
while read -r rds_instance;do
|
||||
RDS_NAME=$(echo $rds_instance | awk '{ print $1; }')
|
||||
RDS_DNSNAME=$(echo $rds_instance | awk '{ print $2; }')
|
||||
textFail "$regx: RDS instance: $RDS_NAME at $RDS_DNSNAME is set as Publicly Accessible!" "$regx" "$RDS_NAME"
|
||||
done <<< "$LIST_OF_RDS_PUBLIC_INSTANCES"
|
||||
else
|
||||
textPass "$regx: no Publicly Accessible RDS instances found" "$regx" "$RDS_NAME"
|
||||
fi
|
||||
done
|
||||
}
|
||||
4
providers/aws/services/rds/rds_client.py
Normal file
4
providers/aws/services/rds/rds_client.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from providers.aws.lib.audit_info.audit_info import current_audit_info
|
||||
from providers.aws.services.rds.rds_service import RDS
|
||||
|
||||
rds_client = RDS(current_audit_info)
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"Provider": "aws",
|
||||
"CheckID": "rds_instance_backup_enabled",
|
||||
"CheckTitle": "Check if RDS instances have backup enabled.",
|
||||
"CheckType": [],
|
||||
"ServiceName": "rds",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "arn:aws:rds:region:account-id:db-instance",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "AwsRdsDbInstance",
|
||||
"Description": "Check if RDS instances have backup enabled.",
|
||||
"Risk": "If backup is not enabled, data is vulnerable. Human error or bad actors could erase or modify data.",
|
||||
"RelatedUrl": "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_WorkingWithAutomatedBackups.html",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "aws rds modify-db-instance --db-instance-identifier <db_instance_id> --backup-retention-period 7 --apply-immediately",
|
||||
"NativeIaC": "",
|
||||
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/RDS/rds-automated-backups-enabled.html",
|
||||
"Terraform": "https://docs.bridgecrew.io/docs/ensure-that-rds-instances-have-backup-policy#terraform"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Enable automated backup for production data. Define a retention period and periodically test backup restoration. A Disaster Recovery process should be in place to govern Data Protection approach.",
|
||||
"Url": "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_WorkingWithAutomatedBackups.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.rds.rds_client import rds_client
|
||||
|
||||
|
||||
class rds_instance_backup_enabled(Check):
|
||||
def execute(self):
|
||||
findings = []
|
||||
for db_instance in rds_client.db_instances:
|
||||
report = Check_Report(self.metadata)
|
||||
report.region = db_instance.region
|
||||
report.resource_id = db_instance.id
|
||||
if db_instance.backup_retention_period > 0:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"RDS Instance {db_instance.id} has backup enabled with retention period {db_instance.backup_retention_period} days."
|
||||
else:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
f"RDS Instance {db_instance.id} has not backup enabled."
|
||||
)
|
||||
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
@@ -0,0 +1,103 @@
|
||||
from re import search
|
||||
from unittest import mock
|
||||
|
||||
from boto3 import client
|
||||
from moto import mock_rds
|
||||
|
||||
AWS_REGION = "us-east-1"
|
||||
|
||||
|
||||
class Test_rds_instance_backup_enabled:
|
||||
@mock_rds
|
||||
def test_rds_no_instances(self):
|
||||
from providers.aws.lib.audit_info.audit_info import current_audit_info
|
||||
from providers.aws.services.rds.rds_service import RDS
|
||||
|
||||
current_audit_info.audited_partition = "aws"
|
||||
|
||||
with mock.patch(
|
||||
"providers.aws.services.rds.rds_instance_backup_enabled.rds_instance_backup_enabled.rds_client",
|
||||
new=RDS(current_audit_info),
|
||||
):
|
||||
# Test Check
|
||||
from providers.aws.services.rds.rds_instance_backup_enabled.rds_instance_backup_enabled import (
|
||||
rds_instance_backup_enabled,
|
||||
)
|
||||
|
||||
check = rds_instance_backup_enabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 0
|
||||
|
||||
@mock_rds
|
||||
def test_rds_instance_no_backup(self):
|
||||
conn = client("rds", region_name=AWS_REGION)
|
||||
conn.create_db_instance(
|
||||
DBInstanceIdentifier="db-master-1",
|
||||
AllocatedStorage=10,
|
||||
Engine="postgres",
|
||||
DBName="staging-postgres",
|
||||
DBInstanceClass="db.m1.small",
|
||||
)
|
||||
from providers.aws.lib.audit_info.audit_info import current_audit_info
|
||||
from providers.aws.services.rds.rds_service import RDS
|
||||
|
||||
current_audit_info.audited_partition = "aws"
|
||||
|
||||
with mock.patch(
|
||||
"providers.aws.services.rds.rds_instance_backup_enabled.rds_instance_backup_enabled.rds_client",
|
||||
new=RDS(current_audit_info),
|
||||
) as service_client:
|
||||
# Test Check
|
||||
from providers.aws.services.rds.rds_instance_backup_enabled.rds_instance_backup_enabled import (
|
||||
rds_instance_backup_enabled,
|
||||
)
|
||||
|
||||
service_client.db_instances[0].backup_retention_period = 0
|
||||
|
||||
check = rds_instance_backup_enabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert search(
|
||||
"has not backup enabled",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == "db-master-1"
|
||||
|
||||
@mock_rds
|
||||
def test_rds_instance_with_backup(self):
|
||||
conn = client("rds", region_name=AWS_REGION)
|
||||
conn.create_db_instance(
|
||||
DBInstanceIdentifier="db-master-1",
|
||||
AllocatedStorage=10,
|
||||
Engine="postgres",
|
||||
DBName="staging-postgres",
|
||||
DBInstanceClass="db.m1.small",
|
||||
BackupRetentionPeriod=10,
|
||||
)
|
||||
from providers.aws.lib.audit_info.audit_info import current_audit_info
|
||||
from providers.aws.services.rds.rds_service import RDS
|
||||
|
||||
current_audit_info.audited_partition = "aws"
|
||||
|
||||
with mock.patch(
|
||||
"providers.aws.services.rds.rds_instance_backup_enabled.rds_instance_backup_enabled.rds_client",
|
||||
new=RDS(current_audit_info),
|
||||
):
|
||||
# Test Check
|
||||
from providers.aws.services.rds.rds_instance_backup_enabled.rds_instance_backup_enabled import (
|
||||
rds_instance_backup_enabled,
|
||||
)
|
||||
|
||||
check = rds_instance_backup_enabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert search(
|
||||
"has backup enabled",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == "db-master-1"
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"Provider": "aws",
|
||||
"CheckID": "rds_instance_deletion_protection",
|
||||
"CheckTitle": "Check if RDS instances have deletion protection enabled.",
|
||||
"CheckType": [],
|
||||
"ServiceName": "rds",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "arn:aws:rds:region:account-id:db-instance",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "AwsRdsDbInstance",
|
||||
"Description": "Check if RDS instances have deletion protection enabled.",
|
||||
"Risk": "You can only delete instances that do not have deletion protection enabled.",
|
||||
"RelatedUrl": "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_DeleteInstance.html",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "aws rds modify-db-instance --db-instance-identifier <db_instance_id> --deletion-protection --apply-immediately",
|
||||
"NativeIaC": "",
|
||||
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/RDS/instance-deletion-protection.html",
|
||||
"Terraform": "https://docs.bridgecrew.io/docs/ensure-that-rds-clusters-and-instances-have-deletion-protection-enabled#terraform"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Enable deletion protection using the AWS Management Console for production DB instances.",
|
||||
"Url": "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_DeleteInstance.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.rds.rds_client import rds_client
|
||||
|
||||
|
||||
class rds_instance_deletion_protection(Check):
|
||||
def execute(self):
|
||||
findings = []
|
||||
for db_instance in rds_client.db_instances:
|
||||
report = Check_Report(self.metadata)
|
||||
report.region = db_instance.region
|
||||
report.resource_id = db_instance.id
|
||||
if db_instance.deletion_protection:
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
f"RDS Instance {db_instance.id} deletion protection is enabled."
|
||||
)
|
||||
else:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
f"RDS Instance {db_instance.id} deletion protection is not enabled."
|
||||
)
|
||||
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
@@ -0,0 +1,101 @@
|
||||
from re import search
|
||||
from unittest import mock
|
||||
|
||||
from boto3 import client
|
||||
from moto import mock_rds
|
||||
|
||||
AWS_REGION = "us-east-1"
|
||||
|
||||
|
||||
class Test_rds_instance_deletion_protection:
|
||||
@mock_rds
|
||||
def test_rds_no_instances(self):
|
||||
from providers.aws.lib.audit_info.audit_info import current_audit_info
|
||||
from providers.aws.services.rds.rds_service import RDS
|
||||
|
||||
current_audit_info.audited_partition = "aws"
|
||||
|
||||
with mock.patch(
|
||||
"providers.aws.services.rds.rds_instance_deletion_protection.rds_instance_deletion_protection.rds_client",
|
||||
new=RDS(current_audit_info),
|
||||
):
|
||||
# Test Check
|
||||
from providers.aws.services.rds.rds_instance_deletion_protection.rds_instance_deletion_protection import (
|
||||
rds_instance_deletion_protection,
|
||||
)
|
||||
|
||||
check = rds_instance_deletion_protection()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 0
|
||||
|
||||
@mock_rds
|
||||
def test_rds_instance_no_deletion_protection(self):
|
||||
conn = client("rds", region_name=AWS_REGION)
|
||||
conn.create_db_instance(
|
||||
DBInstanceIdentifier="db-master-1",
|
||||
AllocatedStorage=10,
|
||||
Engine="postgres",
|
||||
DBName="staging-postgres",
|
||||
DBInstanceClass="db.m1.small",
|
||||
)
|
||||
from providers.aws.lib.audit_info.audit_info import current_audit_info
|
||||
from providers.aws.services.rds.rds_service import RDS
|
||||
|
||||
current_audit_info.audited_partition = "aws"
|
||||
|
||||
with mock.patch(
|
||||
"providers.aws.services.rds.rds_instance_deletion_protection.rds_instance_deletion_protection.rds_client",
|
||||
new=RDS(current_audit_info),
|
||||
):
|
||||
# Test Check
|
||||
from providers.aws.services.rds.rds_instance_deletion_protection.rds_instance_deletion_protection import (
|
||||
rds_instance_deletion_protection,
|
||||
)
|
||||
|
||||
check = rds_instance_deletion_protection()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert search(
|
||||
"deletion protection is not enabled",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == "db-master-1"
|
||||
|
||||
@mock_rds
|
||||
def test_rds_instance_with_encryption(self):
|
||||
conn = client("rds", region_name=AWS_REGION)
|
||||
conn.create_db_instance(
|
||||
DBInstanceIdentifier="db-master-1",
|
||||
AllocatedStorage=10,
|
||||
Engine="postgres",
|
||||
DBName="staging-postgres",
|
||||
DBInstanceClass="db.m1.small",
|
||||
DeletionProtection=True,
|
||||
)
|
||||
from providers.aws.lib.audit_info.audit_info import current_audit_info
|
||||
from providers.aws.services.rds.rds_service import RDS
|
||||
|
||||
current_audit_info.audited_partition = "aws"
|
||||
|
||||
with mock.patch(
|
||||
"providers.aws.services.rds.rds_instance_deletion_protection.rds_instance_deletion_protection.rds_client",
|
||||
new=RDS(current_audit_info),
|
||||
):
|
||||
# Test Check
|
||||
from providers.aws.services.rds.rds_instance_deletion_protection.rds_instance_deletion_protection import (
|
||||
rds_instance_deletion_protection,
|
||||
)
|
||||
|
||||
check = rds_instance_deletion_protection()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert search(
|
||||
"deletion protection is enabled",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == "db-master-1"
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"Provider": "aws",
|
||||
"CheckID": "rds_instance_enhanced_monitoring_enabled",
|
||||
"CheckTitle": "Check if RDS instances has enhanced monitoring enabled.",
|
||||
"CheckType": [],
|
||||
"ServiceName": "rds",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "arn:aws:rds:region:account-id:db-instance",
|
||||
"Severity": "low",
|
||||
"ResourceType": "AwsRdsDbInstance",
|
||||
"Description": "Check if RDS instances has enhanced monitoring enabled.",
|
||||
"Risk": "A smaller monitoring interval results in more frequent reporting of OS metrics.",
|
||||
"RelatedUrl": "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_Monitoring.OS.html",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "aws rds create-db-instance --db-instance-identifier <db_instance_id> --db-instance-class <instance_class> --engine <engine> --storage-encrypted true",
|
||||
"NativeIaC": "",
|
||||
"Other": "",
|
||||
"Terraform": "https://docs.bridgecrew.io/docs/ensure-that-enhanced-monitoring-is-enabled-for-amazon-rds-instances#terraform"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "To use Enhanced Monitoring, you must create an IAM role; and then enable Enhanced Monitoring.",
|
||||
"Url": "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_Monitoring.OS.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.rds.rds_client import rds_client
|
||||
|
||||
|
||||
class rds_instance_enhanced_monitoring_enabled(Check):
|
||||
def execute(self):
|
||||
findings = []
|
||||
for db_instance in rds_client.db_instances:
|
||||
report = Check_Report(self.metadata)
|
||||
report.region = db_instance.region
|
||||
report.resource_id = db_instance.id
|
||||
if db_instance.enhanced_monitoring_arn:
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
f"RDS Instance {db_instance.id} has enhanced monitoring enabled."
|
||||
)
|
||||
else:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"RDS Instance {db_instance.id} does not have enhanced monitoring enabled."
|
||||
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
@@ -0,0 +1,101 @@
|
||||
from re import search
|
||||
from unittest import mock
|
||||
|
||||
from boto3 import client
|
||||
from moto import mock_rds
|
||||
|
||||
AWS_REGION = "us-east-1"
|
||||
|
||||
|
||||
class Test_rds_instance_enhanced_monitoring_enabled:
|
||||
@mock_rds
|
||||
def test_rds_no_instances(self):
|
||||
from providers.aws.lib.audit_info.audit_info import current_audit_info
|
||||
from providers.aws.services.rds.rds_service import RDS
|
||||
|
||||
current_audit_info.audited_partition = "aws"
|
||||
|
||||
with mock.patch(
|
||||
"providers.aws.services.rds.rds_instance_enhanced_monitoring_enabled.rds_instance_enhanced_monitoring_enabled.rds_client",
|
||||
new=RDS(current_audit_info),
|
||||
):
|
||||
# Test Check
|
||||
from providers.aws.services.rds.rds_instance_enhanced_monitoring_enabled.rds_instance_enhanced_monitoring_enabled import (
|
||||
rds_instance_enhanced_monitoring_enabled,
|
||||
)
|
||||
|
||||
check = rds_instance_enhanced_monitoring_enabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 0
|
||||
|
||||
@mock_rds
|
||||
def test_rds_instance_no_monitoring(self):
|
||||
conn = client("rds", region_name=AWS_REGION)
|
||||
conn.create_db_instance(
|
||||
DBInstanceIdentifier="db-master-1",
|
||||
AllocatedStorage=10,
|
||||
Engine="postgres",
|
||||
DBName="staging-postgres",
|
||||
DBInstanceClass="db.m1.small",
|
||||
)
|
||||
from providers.aws.lib.audit_info.audit_info import current_audit_info
|
||||
from providers.aws.services.rds.rds_service import RDS
|
||||
|
||||
current_audit_info.audited_partition = "aws"
|
||||
|
||||
with mock.patch(
|
||||
"providers.aws.services.rds.rds_instance_enhanced_monitoring_enabled.rds_instance_enhanced_monitoring_enabled.rds_client",
|
||||
new=RDS(current_audit_info),
|
||||
):
|
||||
# Test Check
|
||||
from providers.aws.services.rds.rds_instance_enhanced_monitoring_enabled.rds_instance_enhanced_monitoring_enabled import (
|
||||
rds_instance_enhanced_monitoring_enabled,
|
||||
)
|
||||
|
||||
check = rds_instance_enhanced_monitoring_enabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert search(
|
||||
"does not have enhanced monitoring enabled",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == "db-master-1"
|
||||
|
||||
@mock_rds
|
||||
def test_rds_instance_with_monitoring(self):
|
||||
conn = client("rds", region_name=AWS_REGION)
|
||||
conn.create_db_instance(
|
||||
DBInstanceIdentifier="db-master-1",
|
||||
AllocatedStorage=10,
|
||||
Engine="postgres",
|
||||
DBName="staging-postgres",
|
||||
DBInstanceClass="db.m1.small",
|
||||
)
|
||||
from providers.aws.lib.audit_info.audit_info import current_audit_info
|
||||
from providers.aws.services.rds.rds_service import RDS
|
||||
|
||||
current_audit_info.audited_partition = "aws"
|
||||
|
||||
with mock.patch(
|
||||
"providers.aws.services.rds.rds_instance_enhanced_monitoring_enabled.rds_instance_enhanced_monitoring_enabled.rds_client",
|
||||
new=RDS(current_audit_info),
|
||||
) as service_client:
|
||||
# Test Check
|
||||
from providers.aws.services.rds.rds_instance_enhanced_monitoring_enabled.rds_instance_enhanced_monitoring_enabled import (
|
||||
rds_instance_enhanced_monitoring_enabled,
|
||||
)
|
||||
|
||||
service_client.db_instances[0].enhanced_monitoring_arn = "log-stream"
|
||||
check = rds_instance_enhanced_monitoring_enabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert search(
|
||||
"has enhanced monitoring enabled",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == "db-master-1"
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"Provider": "aws",
|
||||
"CheckID": "rds_instance_integration_cloudwatch_logs",
|
||||
"CheckTitle": "Check if RDS instances is integrated with CloudWatch Logs.",
|
||||
"CheckType": [],
|
||||
"ServiceName": "rds",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "arn:aws:rds:region:account-id:db-instance",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "AwsRdsDbInstance",
|
||||
"Description": "Check if RDS instances is integrated with CloudWatch Logs.",
|
||||
"Risk": "If logs are not enabled, monitoring of service use and threat analysis is not possible.",
|
||||
"RelatedUrl": "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/publishing_cloudwatchlogs.html",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "aws rds modify-db-instance --db-instance-identifier <db_instance_id> --cloudwatch-logs-export-configuration {'EnableLogTypes':['audit',error','general','slowquery']} --apply-immediately",
|
||||
"NativeIaC": "",
|
||||
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/RDS/log-exports.html",
|
||||
"Terraform": "https://docs.bridgecrew.io/docs/ensure-that-respective-logs-of-amazon-relational-database-service-amazon-rds-are-enabled#terraform"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Use CloudWatch Logs to perform real-time analysis of the log data. Create alarms and view metrics.",
|
||||
"Url": "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/publishing_cloudwatchlogs.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.rds.rds_client import rds_client
|
||||
|
||||
|
||||
class rds_instance_integration_cloudwatch_logs(Check):
|
||||
def execute(self):
|
||||
findings = []
|
||||
for db_instance in rds_client.db_instances:
|
||||
report = Check_Report(self.metadata)
|
||||
report.region = db_instance.region
|
||||
report.resource_id = db_instance.id
|
||||
if db_instance.cloudwatch_logs:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"RDS Instance {db_instance.id} is shipping {' '.join(db_instance.cloudwatch_logs)} to CloudWatch Logs."
|
||||
else:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"RDS Instance {db_instance.id} does not have CloudWatch Logs enabled."
|
||||
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
@@ -0,0 +1,101 @@
|
||||
from re import search
|
||||
from unittest import mock
|
||||
|
||||
from boto3 import client
|
||||
from moto import mock_rds
|
||||
|
||||
AWS_REGION = "us-east-1"
|
||||
|
||||
|
||||
class Test_rds_instance_integration_cloudwatch_logs:
|
||||
@mock_rds
|
||||
def test_rds_no_instances(self):
|
||||
from providers.aws.lib.audit_info.audit_info import current_audit_info
|
||||
from providers.aws.services.rds.rds_service import RDS
|
||||
|
||||
current_audit_info.audited_partition = "aws"
|
||||
|
||||
with mock.patch(
|
||||
"providers.aws.services.rds.rds_instance_integration_cloudwatch_logs.rds_instance_integration_cloudwatch_logs.rds_client",
|
||||
new=RDS(current_audit_info),
|
||||
):
|
||||
# Test Check
|
||||
from providers.aws.services.rds.rds_instance_integration_cloudwatch_logs.rds_instance_integration_cloudwatch_logs import (
|
||||
rds_instance_integration_cloudwatch_logs,
|
||||
)
|
||||
|
||||
check = rds_instance_integration_cloudwatch_logs()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 0
|
||||
|
||||
@mock_rds
|
||||
def test_rds_instance_no_logs(self):
|
||||
conn = client("rds", region_name=AWS_REGION)
|
||||
conn.create_db_instance(
|
||||
DBInstanceIdentifier="db-master-1",
|
||||
AllocatedStorage=10,
|
||||
Engine="postgres",
|
||||
DBName="staging-postgres",
|
||||
DBInstanceClass="db.m1.small",
|
||||
)
|
||||
from providers.aws.lib.audit_info.audit_info import current_audit_info
|
||||
from providers.aws.services.rds.rds_service import RDS
|
||||
|
||||
current_audit_info.audited_partition = "aws"
|
||||
|
||||
with mock.patch(
|
||||
"providers.aws.services.rds.rds_instance_integration_cloudwatch_logs.rds_instance_integration_cloudwatch_logs.rds_client",
|
||||
new=RDS(current_audit_info),
|
||||
):
|
||||
# Test Check
|
||||
from providers.aws.services.rds.rds_instance_integration_cloudwatch_logs.rds_instance_integration_cloudwatch_logs import (
|
||||
rds_instance_integration_cloudwatch_logs,
|
||||
)
|
||||
|
||||
check = rds_instance_integration_cloudwatch_logs()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert search(
|
||||
"does not have CloudWatch Logs enabled",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == "db-master-1"
|
||||
|
||||
@mock_rds
|
||||
def test_rds_instance_with_logs(self):
|
||||
conn = client("rds", region_name=AWS_REGION)
|
||||
conn.create_db_instance(
|
||||
DBInstanceIdentifier="db-master-1",
|
||||
AllocatedStorage=10,
|
||||
Engine="postgres",
|
||||
DBName="staging-postgres",
|
||||
DBInstanceClass="db.m1.small",
|
||||
EnableCloudwatchLogsExports=["audit", "error"],
|
||||
)
|
||||
from providers.aws.lib.audit_info.audit_info import current_audit_info
|
||||
from providers.aws.services.rds.rds_service import RDS
|
||||
|
||||
current_audit_info.audited_partition = "aws"
|
||||
|
||||
with mock.patch(
|
||||
"providers.aws.services.rds.rds_instance_integration_cloudwatch_logs.rds_instance_integration_cloudwatch_logs.rds_client",
|
||||
new=RDS(current_audit_info),
|
||||
):
|
||||
# Test Check
|
||||
from providers.aws.services.rds.rds_instance_integration_cloudwatch_logs.rds_instance_integration_cloudwatch_logs import (
|
||||
rds_instance_integration_cloudwatch_logs,
|
||||
)
|
||||
|
||||
check = rds_instance_integration_cloudwatch_logs()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert search(
|
||||
"is shipping audit error to CloudWatch Logs",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == "db-master-1"
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"Provider": "aws",
|
||||
"CheckID": "rds_instance_minor_version_upgrade_enabled",
|
||||
"CheckTitle": "Ensure RDS instances have minor version upgrade enabled.",
|
||||
"CheckType": [],
|
||||
"ServiceName": "rds",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "arn:aws:rds:region:account-id:db-instance",
|
||||
"Severity": "low",
|
||||
"ResourceType": "AwsRdsDbInstance",
|
||||
"Description": "Ensure RDS instances have minor version upgrade enabled.",
|
||||
"Risk": "Auto Minor Version Upgrade is a feature that you can enable to have your database automatically upgraded when a new minor database engine version is available. Minor version upgrades often patch security vulnerabilities and fix bugs and therefore should be applied.",
|
||||
"RelatedUrl": "https://aws.amazon.com/blogs/database/best-practices-for-upgrading-amazon-rds-to-major-and-minor-versions-of-postgresql/",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "aws rds modify-db-instance --db-instance-identifier <db_instance_id> --auto-minor-version-upgrade --apply-immediately",
|
||||
"NativeIaC": "https://docs.bridgecrew.io/docs/ensure-aws-db-instance-gets-all-minor-upgrades-automatically#cloudformation",
|
||||
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/RDS/rds-auto-minor-version-upgrade.html",
|
||||
"Terraform": "https://docs.bridgecrew.io/docs/ensure-aws-db-instance-gets-all-minor-upgrades-automatically#terraform"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Enable auto minor version upgrade for all databases and environments.",
|
||||
"Url": "https://aws.amazon.com/blogs/database/best-practices-for-upgrading-amazon-rds-to-major-and-minor-versions-of-postgresql/"
|
||||
}
|
||||
},
|
||||
"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.rds.rds_client import rds_client
|
||||
|
||||
|
||||
class rds_instance_minor_version_upgrade_enabled(Check):
|
||||
def execute(self):
|
||||
findings = []
|
||||
for db_instance in rds_client.db_instances:
|
||||
report = Check_Report(self.metadata)
|
||||
report.region = db_instance.region
|
||||
report.resource_id = db_instance.id
|
||||
if db_instance.auto_minor_version_upgrade:
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
f"RDS Instance {db_instance.id} has minor version upgrade enabled."
|
||||
)
|
||||
else:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"RDS Instance {db_instance.id} does not have minor version upgrade enabled."
|
||||
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
@@ -0,0 +1,101 @@
|
||||
from re import search
|
||||
from unittest import mock
|
||||
|
||||
from boto3 import client
|
||||
from moto import mock_rds
|
||||
|
||||
AWS_REGION = "us-east-1"
|
||||
|
||||
|
||||
class Test_rds_instance_minor_version_upgrade_enabled:
|
||||
@mock_rds
|
||||
def test_rds_no_instances(self):
|
||||
from providers.aws.lib.audit_info.audit_info import current_audit_info
|
||||
from providers.aws.services.rds.rds_service import RDS
|
||||
|
||||
current_audit_info.audited_partition = "aws"
|
||||
|
||||
with mock.patch(
|
||||
"providers.aws.services.rds.rds_instance_minor_version_upgrade_enabled.rds_instance_minor_version_upgrade_enabled.rds_client",
|
||||
new=RDS(current_audit_info),
|
||||
):
|
||||
# Test Check
|
||||
from providers.aws.services.rds.rds_instance_minor_version_upgrade_enabled.rds_instance_minor_version_upgrade_enabled import (
|
||||
rds_instance_minor_version_upgrade_enabled,
|
||||
)
|
||||
|
||||
check = rds_instance_minor_version_upgrade_enabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 0
|
||||
|
||||
@mock_rds
|
||||
def test_rds_instance_no_auto_upgrade(self):
|
||||
conn = client("rds", region_name=AWS_REGION)
|
||||
conn.create_db_instance(
|
||||
DBInstanceIdentifier="db-master-1",
|
||||
AllocatedStorage=10,
|
||||
Engine="postgres",
|
||||
DBName="staging-postgres",
|
||||
DBInstanceClass="db.m1.small",
|
||||
)
|
||||
from providers.aws.lib.audit_info.audit_info import current_audit_info
|
||||
from providers.aws.services.rds.rds_service import RDS
|
||||
|
||||
current_audit_info.audited_partition = "aws"
|
||||
|
||||
with mock.patch(
|
||||
"providers.aws.services.rds.rds_instance_minor_version_upgrade_enabled.rds_instance_minor_version_upgrade_enabled.rds_client",
|
||||
new=RDS(current_audit_info),
|
||||
):
|
||||
# Test Check
|
||||
from providers.aws.services.rds.rds_instance_minor_version_upgrade_enabled.rds_instance_minor_version_upgrade_enabled import (
|
||||
rds_instance_minor_version_upgrade_enabled,
|
||||
)
|
||||
|
||||
check = rds_instance_minor_version_upgrade_enabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert search(
|
||||
"does not have minor version upgrade enabled",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == "db-master-1"
|
||||
|
||||
@mock_rds
|
||||
def test_rds_instance_with_auto_upgrade(self):
|
||||
conn = client("rds", region_name=AWS_REGION)
|
||||
conn.create_db_instance(
|
||||
DBInstanceIdentifier="db-master-1",
|
||||
AllocatedStorage=10,
|
||||
Engine="postgres",
|
||||
DBName="staging-postgres",
|
||||
DBInstanceClass="db.m1.small",
|
||||
AutoMinorVersionUpgrade=True,
|
||||
)
|
||||
from providers.aws.lib.audit_info.audit_info import current_audit_info
|
||||
from providers.aws.services.rds.rds_service import RDS
|
||||
|
||||
current_audit_info.audited_partition = "aws"
|
||||
|
||||
with mock.patch(
|
||||
"providers.aws.services.rds.rds_instance_minor_version_upgrade_enabled.rds_instance_minor_version_upgrade_enabled.rds_client",
|
||||
new=RDS(current_audit_info),
|
||||
):
|
||||
# Test Check
|
||||
from providers.aws.services.rds.rds_instance_minor_version_upgrade_enabled.rds_instance_minor_version_upgrade_enabled import (
|
||||
rds_instance_minor_version_upgrade_enabled,
|
||||
)
|
||||
|
||||
check = rds_instance_minor_version_upgrade_enabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert search(
|
||||
"has minor version upgrade enabled",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == "db-master-1"
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"Provider": "aws",
|
||||
"CheckID": "rds_instance_multi_az",
|
||||
"CheckTitle": "Check if RDS instances have multi-AZ enabled.",
|
||||
"CheckType": [],
|
||||
"ServiceName": "rds",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "arn:aws:rds:region:account-id:db-instance",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "AwsRdsDbInstance",
|
||||
"Description": "Check if RDS instances have multi-AZ enabled.",
|
||||
"Risk": "In case of failure, with a single-AZ deployment configuration, should an availability zone specific database failure occur, Amazon RDS can not automatically fail over to the standby availability zone.",
|
||||
"RelatedUrl": "https://aws.amazon.com/rds/features/multi-az/",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "aws rds create-db-instance --db-instance-identifier <db_instance_id> --multi-az true",
|
||||
"NativeIaC": "https://docs.bridgecrew.io/docs/general_73#cloudformation",
|
||||
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/RDS/rds-multi-az.html",
|
||||
"Terraform": "https://docs.bridgecrew.io/docs/general_73#terraform"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Enable multi-AZ deployment for production databases.",
|
||||
"Url": "https://aws.amazon.com/rds/features/multi-az/"
|
||||
}
|
||||
},
|
||||
"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.rds.rds_client import rds_client
|
||||
|
||||
|
||||
class rds_instance_multi_az(Check):
|
||||
def execute(self):
|
||||
findings = []
|
||||
for db_instance in rds_client.db_instances:
|
||||
report = Check_Report(self.metadata)
|
||||
report.region = db_instance.region
|
||||
report.resource_id = db_instance.id
|
||||
if db_instance.multi_az:
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
f"RDS Instance {db_instance.id} has multi-AZ enabled."
|
||||
)
|
||||
else:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
f"RDS Instance {db_instance.id} does not have multi-AZ enabled."
|
||||
)
|
||||
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
@@ -0,0 +1,101 @@
|
||||
from re import search
|
||||
from unittest import mock
|
||||
|
||||
from boto3 import client
|
||||
from moto import mock_rds
|
||||
|
||||
AWS_REGION = "us-east-1"
|
||||
|
||||
|
||||
class Test_rds_instance_multi_az:
|
||||
@mock_rds
|
||||
def test_rds_no_instances(self):
|
||||
from providers.aws.lib.audit_info.audit_info import current_audit_info
|
||||
from providers.aws.services.rds.rds_service import RDS
|
||||
|
||||
current_audit_info.audited_partition = "aws"
|
||||
|
||||
with mock.patch(
|
||||
"providers.aws.services.rds.rds_instance_multi_az.rds_instance_multi_az.rds_client",
|
||||
new=RDS(current_audit_info),
|
||||
):
|
||||
# Test Check
|
||||
from providers.aws.services.rds.rds_instance_multi_az.rds_instance_multi_az import (
|
||||
rds_instance_multi_az,
|
||||
)
|
||||
|
||||
check = rds_instance_multi_az()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 0
|
||||
|
||||
@mock_rds
|
||||
def test_rds_instance_no_multi_az(self):
|
||||
conn = client("rds", region_name=AWS_REGION)
|
||||
conn.create_db_instance(
|
||||
DBInstanceIdentifier="db-master-1",
|
||||
AllocatedStorage=10,
|
||||
Engine="postgres",
|
||||
DBName="staging-postgres",
|
||||
DBInstanceClass="db.m1.small",
|
||||
)
|
||||
from providers.aws.lib.audit_info.audit_info import current_audit_info
|
||||
from providers.aws.services.rds.rds_service import RDS
|
||||
|
||||
current_audit_info.audited_partition = "aws"
|
||||
|
||||
with mock.patch(
|
||||
"providers.aws.services.rds.rds_instance_multi_az.rds_instance_multi_az.rds_client",
|
||||
new=RDS(current_audit_info),
|
||||
):
|
||||
# Test Check
|
||||
from providers.aws.services.rds.rds_instance_multi_az.rds_instance_multi_az import (
|
||||
rds_instance_multi_az,
|
||||
)
|
||||
|
||||
check = rds_instance_multi_az()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert search(
|
||||
"does not have multi-AZ enabled",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == "db-master-1"
|
||||
|
||||
@mock_rds
|
||||
def test_rds_instance_multi_az(self):
|
||||
conn = client("rds", region_name=AWS_REGION)
|
||||
conn.create_db_instance(
|
||||
DBInstanceIdentifier="db-master-1",
|
||||
AllocatedStorage=10,
|
||||
Engine="postgres",
|
||||
DBName="staging-postgres",
|
||||
DBInstanceClass="db.m1.small",
|
||||
MultiAZ=True,
|
||||
)
|
||||
from providers.aws.lib.audit_info.audit_info import current_audit_info
|
||||
from providers.aws.services.rds.rds_service import RDS
|
||||
|
||||
current_audit_info.audited_partition = "aws"
|
||||
|
||||
with mock.patch(
|
||||
"providers.aws.services.rds.rds_instance_multi_az.rds_instance_multi_az.rds_client",
|
||||
new=RDS(current_audit_info),
|
||||
):
|
||||
# Test Check
|
||||
from providers.aws.services.rds.rds_instance_multi_az.rds_instance_multi_az import (
|
||||
rds_instance_multi_az,
|
||||
)
|
||||
|
||||
check = rds_instance_multi_az()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert search(
|
||||
"has multi-AZ enabled",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == "db-master-1"
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"Provider": "aws",
|
||||
"CheckID": "rds_instance_no_public_access",
|
||||
"CheckTitle": "Ensure there are no Public Accessible RDS instances.",
|
||||
"CheckType": [],
|
||||
"ServiceName": "rds",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "arn:aws:rds:region:account-id:db-instance",
|
||||
"Severity": "critical",
|
||||
"ResourceType": "AwsRdsDbInstance",
|
||||
"Description": "Ensure there are no Public Accessible RDS instances.",
|
||||
"Risk": "Publicly accessible databases could expose sensitive data to bad actors.",
|
||||
"RelatedUrl": "https://docs.amazonaws.cn/en_us/config/latest/developerguide/rds-instance-public-access-check.html",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "aws rds modify-db-instance --db-instance-identifier <db_instance_id> --no-publicly-accessible --apply-immediately",
|
||||
"NativeIaC": "https://docs.bridgecrew.io/docs/public_2#cloudformation",
|
||||
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/RDS/rds-publicly-accessible.html",
|
||||
"Terraform": "https://docs.bridgecrew.io/docs/public_2#terraform"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Using an AWS Config rule check for RDS public instances periodically and check there is a business reason for it.",
|
||||
"Url": "https://docs.amazonaws.cn/en_us/config/latest/developerguide/rds-instance-public-access-check.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.rds.rds_client import rds_client
|
||||
|
||||
|
||||
class rds_instance_no_public_access(Check):
|
||||
def execute(self):
|
||||
findings = []
|
||||
for db_instance in rds_client.db_instances:
|
||||
report = Check_Report(self.metadata)
|
||||
report.region = db_instance.region
|
||||
report.resource_id = db_instance.id
|
||||
if not db_instance.public:
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
f"RDS Instance {db_instance.id} is not Publicly Accessible."
|
||||
)
|
||||
else:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
f"RDS Instance {db_instance.id} is set as Publicly Accessible."
|
||||
)
|
||||
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
@@ -0,0 +1,101 @@
|
||||
from re import search
|
||||
from unittest import mock
|
||||
|
||||
from boto3 import client
|
||||
from moto import mock_rds
|
||||
|
||||
AWS_REGION = "us-east-1"
|
||||
|
||||
|
||||
class Test_rds_instance_no_public_access:
|
||||
@mock_rds
|
||||
def test_rds_no_instances(self):
|
||||
from providers.aws.lib.audit_info.audit_info import current_audit_info
|
||||
from providers.aws.services.rds.rds_service import RDS
|
||||
|
||||
current_audit_info.audited_partition = "aws"
|
||||
|
||||
with mock.patch(
|
||||
"providers.aws.services.rds.rds_instance_no_public_access.rds_instance_no_public_access.rds_client",
|
||||
new=RDS(current_audit_info),
|
||||
):
|
||||
# Test Check
|
||||
from providers.aws.services.rds.rds_instance_no_public_access.rds_instance_no_public_access import (
|
||||
rds_instance_no_public_access,
|
||||
)
|
||||
|
||||
check = rds_instance_no_public_access()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 0
|
||||
|
||||
@mock_rds
|
||||
def test_rds_instance_private(self):
|
||||
conn = client("rds", region_name=AWS_REGION)
|
||||
conn.create_db_instance(
|
||||
DBInstanceIdentifier="db-master-1",
|
||||
AllocatedStorage=10,
|
||||
Engine="postgres",
|
||||
DBName="staging-postgres",
|
||||
DBInstanceClass="db.m1.small",
|
||||
)
|
||||
from providers.aws.lib.audit_info.audit_info import current_audit_info
|
||||
from providers.aws.services.rds.rds_service import RDS
|
||||
|
||||
current_audit_info.audited_partition = "aws"
|
||||
|
||||
with mock.patch(
|
||||
"providers.aws.services.rds.rds_instance_no_public_access.rds_instance_no_public_access.rds_client",
|
||||
new=RDS(current_audit_info),
|
||||
):
|
||||
# Test Check
|
||||
from providers.aws.services.rds.rds_instance_no_public_access.rds_instance_no_public_access import (
|
||||
rds_instance_no_public_access,
|
||||
)
|
||||
|
||||
check = rds_instance_no_public_access()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert search(
|
||||
"is not Publicly Accessible",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == "db-master-1"
|
||||
|
||||
@mock_rds
|
||||
def test_rds_instance_public(self):
|
||||
conn = client("rds", region_name=AWS_REGION)
|
||||
conn.create_db_instance(
|
||||
DBInstanceIdentifier="db-master-1",
|
||||
AllocatedStorage=10,
|
||||
Engine="postgres",
|
||||
DBName="staging-postgres",
|
||||
DBInstanceClass="db.m1.small",
|
||||
PubliclyAccessible=True,
|
||||
)
|
||||
from providers.aws.lib.audit_info.audit_info import current_audit_info
|
||||
from providers.aws.services.rds.rds_service import RDS
|
||||
|
||||
current_audit_info.audited_partition = "aws"
|
||||
|
||||
with mock.patch(
|
||||
"providers.aws.services.rds.rds_instance_no_public_access.rds_instance_no_public_access.rds_client",
|
||||
new=RDS(current_audit_info),
|
||||
):
|
||||
# Test Check
|
||||
from providers.aws.services.rds.rds_instance_no_public_access.rds_instance_no_public_access import (
|
||||
rds_instance_no_public_access,
|
||||
)
|
||||
|
||||
check = rds_instance_no_public_access()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert search(
|
||||
"is set as Publicly Accessible",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == "db-master-1"
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"Provider": "aws",
|
||||
"CheckID": "rds_instance_storage_encrypted",
|
||||
"CheckTitle": "Check if RDS instances storage is encrypted.",
|
||||
"CheckType": [],
|
||||
"ServiceName": "rds",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "arn:aws:rds:region:account-id:db-instance",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "AwsRdsDbInstance",
|
||||
"Description": "Check if RDS instances storage is encrypted.",
|
||||
"Risk": "If not enabled sensitive information at rest is not protected.",
|
||||
"RelatedUrl": "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Overview.Encryption.html",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "aws rds create-db-instance --db-instance-identifier <db_instance_id> --db-instance-class <instance_class> --engine <engine> --storage-encrypted true",
|
||||
"NativeIaC": "https://docs.bridgecrew.io/docs/general_4#cloudformation",
|
||||
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/RDS/rds-encryption-enabled.html",
|
||||
"Terraform": "https://docs.bridgecrew.io/docs/general_4#terraform"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Enable Encryption. Use a CMK where possible. It will provide additional management and privacy benefits.",
|
||||
"Url": "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Overview.Encryption.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.rds.rds_client import rds_client
|
||||
|
||||
|
||||
class rds_instance_storage_encrypted(Check):
|
||||
def execute(self):
|
||||
findings = []
|
||||
for db_instance in rds_client.db_instances:
|
||||
report = Check_Report(self.metadata)
|
||||
report.region = db_instance.region
|
||||
report.resource_id = db_instance.id
|
||||
if db_instance.encrypted:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"RDS Instance {db_instance.id} is encrypted."
|
||||
else:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
f"RDS Instance {db_instance.id} is not encrypted."
|
||||
)
|
||||
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
@@ -0,0 +1,101 @@
|
||||
from re import search
|
||||
from unittest import mock
|
||||
|
||||
from boto3 import client
|
||||
from moto import mock_rds
|
||||
|
||||
AWS_REGION = "us-east-1"
|
||||
|
||||
|
||||
class Test_rds_instance_storage_encrypted:
|
||||
@mock_rds
|
||||
def test_rds_no_instances(self):
|
||||
from providers.aws.lib.audit_info.audit_info import current_audit_info
|
||||
from providers.aws.services.rds.rds_service import RDS
|
||||
|
||||
current_audit_info.audited_partition = "aws"
|
||||
|
||||
with mock.patch(
|
||||
"providers.aws.services.rds.rds_instance_storage_encrypted.rds_instance_storage_encrypted.rds_client",
|
||||
new=RDS(current_audit_info),
|
||||
):
|
||||
# Test Check
|
||||
from providers.aws.services.rds.rds_instance_storage_encrypted.rds_instance_storage_encrypted import (
|
||||
rds_instance_storage_encrypted,
|
||||
)
|
||||
|
||||
check = rds_instance_storage_encrypted()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 0
|
||||
|
||||
@mock_rds
|
||||
def test_rds_instance_no_encryption(self):
|
||||
conn = client("rds", region_name=AWS_REGION)
|
||||
conn.create_db_instance(
|
||||
DBInstanceIdentifier="db-master-1",
|
||||
AllocatedStorage=10,
|
||||
Engine="postgres",
|
||||
DBName="staging-postgres",
|
||||
DBInstanceClass="db.m1.small",
|
||||
)
|
||||
from providers.aws.lib.audit_info.audit_info import current_audit_info
|
||||
from providers.aws.services.rds.rds_service import RDS
|
||||
|
||||
current_audit_info.audited_partition = "aws"
|
||||
|
||||
with mock.patch(
|
||||
"providers.aws.services.rds.rds_instance_storage_encrypted.rds_instance_storage_encrypted.rds_client",
|
||||
new=RDS(current_audit_info),
|
||||
):
|
||||
# Test Check
|
||||
from providers.aws.services.rds.rds_instance_storage_encrypted.rds_instance_storage_encrypted import (
|
||||
rds_instance_storage_encrypted,
|
||||
)
|
||||
|
||||
check = rds_instance_storage_encrypted()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert search(
|
||||
"is not encrypted",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == "db-master-1"
|
||||
|
||||
@mock_rds
|
||||
def test_rds_instance_with_encryption(self):
|
||||
conn = client("rds", region_name=AWS_REGION)
|
||||
conn.create_db_instance(
|
||||
DBInstanceIdentifier="db-master-1",
|
||||
AllocatedStorage=10,
|
||||
Engine="postgres",
|
||||
DBName="staging-postgres",
|
||||
DBInstanceClass="db.m1.small",
|
||||
StorageEncrypted=True,
|
||||
)
|
||||
from providers.aws.lib.audit_info.audit_info import current_audit_info
|
||||
from providers.aws.services.rds.rds_service import RDS
|
||||
|
||||
current_audit_info.audited_partition = "aws"
|
||||
|
||||
with mock.patch(
|
||||
"providers.aws.services.rds.rds_instance_storage_encrypted.rds_instance_storage_encrypted.rds_client",
|
||||
new=RDS(current_audit_info),
|
||||
):
|
||||
# Test Check
|
||||
from providers.aws.services.rds.rds_instance_storage_encrypted.rds_instance_storage_encrypted import (
|
||||
rds_instance_storage_encrypted,
|
||||
)
|
||||
|
||||
check = rds_instance_storage_encrypted()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert search(
|
||||
"is encrypted",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == "db-master-1"
|
||||
176
providers/aws/services/rds/rds_service.py
Normal file
176
providers/aws/services/rds/rds_service.py
Normal file
@@ -0,0 +1,176 @@
|
||||
import threading
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from lib.logger import logger
|
||||
from providers.aws.aws_provider import generate_regional_clients
|
||||
|
||||
|
||||
################## RDS
|
||||
class RDS:
|
||||
def __init__(self, audit_info):
|
||||
self.service = "rds"
|
||||
self.session = audit_info.audit_session
|
||||
self.audited_account = audit_info.audited_account
|
||||
self.regional_clients = generate_regional_clients(self.service, audit_info)
|
||||
self.db_instances = []
|
||||
self.db_snapshots = []
|
||||
self.db_cluster_snapshots = []
|
||||
self.__threading_call__(self.__describe_db_instances__)
|
||||
self.__threading_call__(self.__describe_db_snapshots__)
|
||||
self.__threading_call__(self.__describe_db_snapshot_attributes__)
|
||||
self.__threading_call__(self.__describe_db_cluster_snapshots__)
|
||||
self.__threading_call__(self.__describe_db_cluster_snapshot_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_db_instances__(self, regional_client):
|
||||
logger.info("RDS - Describe Instances...")
|
||||
try:
|
||||
describe_db_instances_paginator = regional_client.get_paginator(
|
||||
"describe_db_instances"
|
||||
)
|
||||
for page in describe_db_instances_paginator.paginate():
|
||||
for instance in page["DBInstances"]:
|
||||
self.db_instances.append(
|
||||
DBInstance(
|
||||
id=instance["DBInstanceIdentifier"],
|
||||
endpoint=instance["Endpoint"]["Address"],
|
||||
status=instance["DBInstanceStatus"],
|
||||
public=instance["PubliclyAccessible"],
|
||||
encrypted=instance["StorageEncrypted"],
|
||||
auto_minor_version_upgrade=instance[
|
||||
"AutoMinorVersionUpgrade"
|
||||
],
|
||||
backup_retention_period=instance.get(
|
||||
"BackupRetentionPeriod"
|
||||
),
|
||||
cloudwatch_logs=instance.get(
|
||||
"EnabledCloudwatchLogsExports"
|
||||
),
|
||||
deletion_protection=instance["DeletionProtection"],
|
||||
enhanced_monitoring_arn=instance.get(
|
||||
"EnhancedMonitoringResourceArn"
|
||||
),
|
||||
multi_az=instance["MultiAZ"],
|
||||
region=regional_client.region,
|
||||
)
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
|
||||
def __describe_db_snapshots__(self, regional_client):
|
||||
logger.info("RDS - Describe Snapshots...")
|
||||
try:
|
||||
describe_db_snapshots_paginator = regional_client.get_paginator(
|
||||
"describe_db_snapshots"
|
||||
)
|
||||
for page in describe_db_snapshots_paginator.paginate():
|
||||
for snapshot in page["DBSnapshots"]:
|
||||
self.db_snapshots.append(
|
||||
DBSnapshot(
|
||||
id=snapshot["DBSnapshotIdentifier"],
|
||||
instance_id=snapshot["DBInstanceIdentifier"],
|
||||
region=regional_client.region,
|
||||
)
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
|
||||
def __describe_db_snapshot_attributes__(self, regional_client):
|
||||
logger.info("RDS - Describe Snapshot Attributes...")
|
||||
try:
|
||||
for snapshot in self.db_snapshots:
|
||||
if snapshot.region == regional_client.region:
|
||||
response = regional_client.describe_db_snapshot_attributes(
|
||||
DBSnapshotIdentifier=snapshot.id
|
||||
)["DBSnapshotAttributesResult"]
|
||||
for att in response["DBSnapshotAttributes"]:
|
||||
if "all" in att["AttributeValues"]:
|
||||
snapshot.public = True
|
||||
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
|
||||
def __describe_db_cluster_snapshots__(self, regional_client):
|
||||
logger.info("RDS - Describe Cluster Snapshots...")
|
||||
try:
|
||||
describe_db_snapshots_paginator = regional_client.get_paginator(
|
||||
"describe_db_cluster_snapshots"
|
||||
)
|
||||
for page in describe_db_snapshots_paginator.paginate():
|
||||
for snapshot in page["DBClusterSnapshots"]:
|
||||
self.db_cluster_snapshots.append(
|
||||
ClusterSnapshot(
|
||||
id=snapshot["DBClusterSnapshotIdentifier"],
|
||||
cluster_id=snapshot["DBClusterIdentifier"],
|
||||
region=regional_client.region,
|
||||
)
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
|
||||
def __describe_db_cluster_snapshot_attributes__(self, regional_client):
|
||||
logger.info("RDS - Describe Cluster Snapshot Attributes...")
|
||||
try:
|
||||
for snapshot in self.db_cluster_snapshots:
|
||||
if snapshot.region == regional_client.region:
|
||||
response = regional_client.describe_db_cluster_snapshot_attributes(
|
||||
DBClusterSnapshotIdentifier=snapshot.id
|
||||
)["DBClusterSnapshotAttributesResult"]
|
||||
for att in response["DBClusterSnapshotAttributes"]:
|
||||
if "all" in att["AttributeValues"]:
|
||||
snapshot.public = True
|
||||
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
|
||||
|
||||
class DBInstance(BaseModel):
|
||||
id: str
|
||||
endpoint: str
|
||||
status: str
|
||||
public: bool
|
||||
encrypted: bool
|
||||
backup_retention_period: int = 0
|
||||
cloudwatch_logs: Optional[list]
|
||||
deletion_protection: bool
|
||||
auto_minor_version_upgrade: bool
|
||||
enhanced_monitoring_arn: Optional[str]
|
||||
multi_az: bool
|
||||
region: str
|
||||
|
||||
|
||||
class DBSnapshot(BaseModel):
|
||||
id: str
|
||||
instance_id: str
|
||||
public: bool = False
|
||||
region: str
|
||||
|
||||
|
||||
class ClusterSnapshot(BaseModel):
|
||||
id: str
|
||||
cluster_id: str
|
||||
public: bool = False
|
||||
region: str
|
||||
150
providers/aws/services/rds/rds_service_test.py
Normal file
150
providers/aws/services/rds/rds_service_test.py
Normal file
@@ -0,0 +1,150 @@
|
||||
from boto3 import client, session
|
||||
from moto import mock_rds
|
||||
|
||||
from providers.aws.lib.audit_info.models import AWS_Audit_Info
|
||||
from providers.aws.services.rds.rds_service import RDS
|
||||
|
||||
AWS_ACCOUNT_NUMBER = 123456789012
|
||||
AWS_REGION = "us-east-1"
|
||||
|
||||
|
||||
class Test_RDS_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 Dynamo Service
|
||||
@mock_rds
|
||||
def test_service(self):
|
||||
# Dynamo client for this test class
|
||||
audit_info = self.set_mocked_audit_info()
|
||||
rds = RDS(audit_info)
|
||||
assert rds.service == "rds"
|
||||
|
||||
# Test Dynamo Client
|
||||
@mock_rds
|
||||
def test_client(self):
|
||||
# Dynamo client for this test class
|
||||
audit_info = self.set_mocked_audit_info()
|
||||
rds = RDS(audit_info)
|
||||
for regional_client in rds.regional_clients.values():
|
||||
assert regional_client.__class__.__name__ == "RDS"
|
||||
|
||||
# Test Dynamo Session
|
||||
@mock_rds
|
||||
def test__get_session__(self):
|
||||
# Dynamo client for this test class
|
||||
audit_info = self.set_mocked_audit_info()
|
||||
rds = RDS(audit_info)
|
||||
assert rds.session.__class__.__name__ == "Session"
|
||||
|
||||
# Test Dynamo Session
|
||||
@mock_rds
|
||||
def test_audited_account(self):
|
||||
# Dynamo client for this test class
|
||||
audit_info = self.set_mocked_audit_info()
|
||||
rds = RDS(audit_info)
|
||||
assert rds.audited_account == AWS_ACCOUNT_NUMBER
|
||||
|
||||
# Test RDS Describe DB Instances
|
||||
@mock_rds
|
||||
def test__describe_db_instances__(self):
|
||||
conn = client("rds", region_name=AWS_REGION)
|
||||
conn.create_db_instance(
|
||||
DBInstanceIdentifier="db-master-1",
|
||||
AllocatedStorage=10,
|
||||
Engine="postgres",
|
||||
DBName="staging-postgres",
|
||||
DBInstanceClass="db.m1.small",
|
||||
StorageEncrypted=True,
|
||||
DeletionProtection=True,
|
||||
PubliclyAccessible=True,
|
||||
AutoMinorVersionUpgrade=True,
|
||||
BackupRetentionPeriod=10,
|
||||
EnableCloudwatchLogsExports=["audit", "error"],
|
||||
MultiAZ=True,
|
||||
)
|
||||
# RDS client for this test class
|
||||
audit_info = self.set_mocked_audit_info()
|
||||
rds = RDS(audit_info)
|
||||
assert len(rds.db_instances) == 1
|
||||
assert rds.db_instances[0].id == "db-master-1"
|
||||
assert rds.db_instances[0].region == AWS_REGION
|
||||
assert (
|
||||
rds.db_instances[0].endpoint
|
||||
== "db-master-1.aaaaaaaaaa.us-east-1.rds.amazonaws.com"
|
||||
)
|
||||
assert rds.db_instances[0].status == "available"
|
||||
assert rds.db_instances[0].public
|
||||
assert rds.db_instances[0].encrypted
|
||||
assert rds.db_instances[0].backup_retention_period == 10
|
||||
assert rds.db_instances[0].cloudwatch_logs == ["audit", "error"]
|
||||
assert rds.db_instances[0].deletion_protection
|
||||
assert rds.db_instances[0].auto_minor_version_upgrade
|
||||
assert rds.db_instances[0].multi_az
|
||||
|
||||
# Test RDS Describe DB Snapshots
|
||||
@mock_rds
|
||||
def test__describe_db_snapshots__(self):
|
||||
conn = client("rds", region_name=AWS_REGION)
|
||||
conn.create_db_instance(
|
||||
DBInstanceIdentifier="db-primary-1",
|
||||
AllocatedStorage=10,
|
||||
Engine="postgres",
|
||||
DBName="staging-postgres",
|
||||
DBInstanceClass="db.m1.small",
|
||||
)
|
||||
|
||||
conn.create_db_snapshot(
|
||||
DBInstanceIdentifier="db-primary-1", DBSnapshotIdentifier="snapshot-1"
|
||||
)
|
||||
# RDS client for this test class
|
||||
audit_info = self.set_mocked_audit_info()
|
||||
rds = RDS(audit_info)
|
||||
assert len(rds.db_snapshots) == 1
|
||||
assert rds.db_snapshots[0].id == "snapshot-1"
|
||||
assert rds.db_snapshots[0].instance_id == "db-primary-1"
|
||||
assert rds.db_snapshots[0].region == AWS_REGION
|
||||
assert not rds.db_snapshots[0].public
|
||||
|
||||
# Test RDS Describe DB Cluster Snapshots
|
||||
@mock_rds
|
||||
def test__describe_db_cluster_snapshots__(self):
|
||||
conn = client("rds", region_name=AWS_REGION)
|
||||
conn.create_db_cluster(
|
||||
DBClusterIdentifier="db-primary-1",
|
||||
AllocatedStorage=10,
|
||||
Engine="postgres",
|
||||
DBClusterInstanceClass="db.m1.small",
|
||||
MasterUsername="root",
|
||||
MasterUserPassword="hunter2000",
|
||||
)
|
||||
|
||||
conn.create_db_cluster_snapshot(
|
||||
DBClusterIdentifier="db-primary-1", DBClusterSnapshotIdentifier="snapshot-1"
|
||||
)
|
||||
# RDS client for this test class
|
||||
audit_info = self.set_mocked_audit_info()
|
||||
rds = RDS(audit_info)
|
||||
assert len(rds.db_cluster_snapshots) == 1
|
||||
assert rds.db_cluster_snapshots[0].id == "snapshot-1"
|
||||
assert rds.db_cluster_snapshots[0].cluster_id == "db-primary-1"
|
||||
assert rds.db_cluster_snapshots[0].region == AWS_REGION
|
||||
assert not rds.db_cluster_snapshots[0].public
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"Provider": "aws",
|
||||
"CheckID": "rds_snapshots_public_access",
|
||||
"CheckTitle": "Check if RDS Snapshots and Cluster Snapshots are public.",
|
||||
"CheckType": [],
|
||||
"ServiceName": "rds",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "arn:aws:rds:region:account-id:snapshot",
|
||||
"Severity": "critical",
|
||||
"ResourceType": "AwsRdsDbSnapshot",
|
||||
"Description": "Check if RDS Snapshots and Cluster Snapshots are public.",
|
||||
"Risk": "Publicly accessible services could expose sensitive data to bad actors. t is recommended that your RDS snapshots should not be public in order to prevent potential leak or misuse of sensitive data or any other kind of security threat. If your RDS snapshot is public, then the data which is backed up in that snapshot is accessible to all other AWS accounts.",
|
||||
"RelatedUrl": "https://docs.aws.amazon.com/config/latest/developerguide/rds-snapshots-public-prohibited.html",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "aws rds modify-db-snapshot-attribute --db-snapshot-identifier <snapshot_id> --attribute-name restore --values-to-remove all",
|
||||
"NativeIaC": "",
|
||||
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/RDS/public-snapshots.html",
|
||||
"Terraform": ""
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Use AWS Config to identify any snapshot that is public.",
|
||||
"Url": "https://docs.aws.amazon.com/config/latest/developerguide/rds-snapshots-public-prohibited.html"
|
||||
}
|
||||
},
|
||||
"Categories": [],
|
||||
"Tags": {
|
||||
"Tag1Key": "value",
|
||||
"Tag2Key": "value"
|
||||
},
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": "",
|
||||
"Compliance": []
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
from lib.check.models import Check, Check_Report
|
||||
from providers.aws.services.rds.rds_client import rds_client
|
||||
|
||||
|
||||
class rds_snapshots_public_access(Check):
|
||||
def execute(self):
|
||||
findings = []
|
||||
for db_snap in rds_client.db_snapshots:
|
||||
report = Check_Report(self.metadata)
|
||||
report.region = db_snap.region
|
||||
report.resource_id = db_snap.id
|
||||
if db_snap.public:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
f"RDS Instance Snapshot {db_snap.id} is public."
|
||||
)
|
||||
else:
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
f"RDS Instance Snapshot {db_snap.id} is not shared."
|
||||
)
|
||||
|
||||
findings.append(report)
|
||||
|
||||
for db_snap in rds_client.db_cluster_snapshots:
|
||||
report = Check_Report(self.metadata)
|
||||
report.region = db_snap.region
|
||||
report.resource_id = db_snap.id
|
||||
if db_snap.public:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"RDS Cluster Snapshot {db_snap.id} is public."
|
||||
else:
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
f"RDS Cluster Snapshot {db_snap.id} is not shared."
|
||||
)
|
||||
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
@@ -0,0 +1,190 @@
|
||||
from re import search
|
||||
from unittest import mock
|
||||
|
||||
from boto3 import client
|
||||
from moto import mock_rds
|
||||
|
||||
AWS_REGION = "us-east-1"
|
||||
|
||||
|
||||
class Test_rds_snapshots_public_access:
|
||||
@mock_rds
|
||||
def test_rds_no_snapshots(self):
|
||||
from providers.aws.lib.audit_info.audit_info import current_audit_info
|
||||
from providers.aws.services.rds.rds_service import RDS
|
||||
|
||||
current_audit_info.audited_partition = "aws"
|
||||
|
||||
with mock.patch(
|
||||
"providers.aws.services.rds.rds_snapshots_public_access.rds_snapshots_public_access.rds_client",
|
||||
new=RDS(current_audit_info),
|
||||
):
|
||||
# Test Check
|
||||
from providers.aws.services.rds.rds_snapshots_public_access.rds_snapshots_public_access import (
|
||||
rds_snapshots_public_access,
|
||||
)
|
||||
|
||||
check = rds_snapshots_public_access()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 0
|
||||
|
||||
@mock_rds
|
||||
def test_rds_private_snapshot(self):
|
||||
conn = client("rds", region_name=AWS_REGION)
|
||||
conn.create_db_instance(
|
||||
DBInstanceIdentifier="db-primary-1",
|
||||
AllocatedStorage=10,
|
||||
Engine="postgres",
|
||||
DBName="staging-postgres",
|
||||
DBInstanceClass="db.m1.small",
|
||||
)
|
||||
|
||||
conn.create_db_snapshot(
|
||||
DBInstanceIdentifier="db-primary-1", DBSnapshotIdentifier="snapshot-1"
|
||||
)
|
||||
from providers.aws.lib.audit_info.audit_info import current_audit_info
|
||||
from providers.aws.services.rds.rds_service import RDS
|
||||
|
||||
current_audit_info.audited_partition = "aws"
|
||||
|
||||
with mock.patch(
|
||||
"providers.aws.services.rds.rds_snapshots_public_access.rds_snapshots_public_access.rds_client",
|
||||
new=RDS(current_audit_info),
|
||||
):
|
||||
# Test Check
|
||||
from providers.aws.services.rds.rds_snapshots_public_access.rds_snapshots_public_access import (
|
||||
rds_snapshots_public_access,
|
||||
)
|
||||
|
||||
check = rds_snapshots_public_access()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert search(
|
||||
"is not shared",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == "snapshot-1"
|
||||
|
||||
@mock_rds
|
||||
def test_rds_public_snapshot(self):
|
||||
conn = client("rds", region_name=AWS_REGION)
|
||||
conn.create_db_instance(
|
||||
DBInstanceIdentifier="db-primary-1",
|
||||
AllocatedStorage=10,
|
||||
Engine="postgres",
|
||||
DBName="staging-postgres",
|
||||
DBInstanceClass="db.m1.small",
|
||||
)
|
||||
|
||||
conn.create_db_snapshot(
|
||||
DBInstanceIdentifier="db-primary-1", DBSnapshotIdentifier="snapshot-1"
|
||||
)
|
||||
from providers.aws.lib.audit_info.audit_info import current_audit_info
|
||||
from providers.aws.services.rds.rds_service import RDS
|
||||
|
||||
current_audit_info.audited_partition = "aws"
|
||||
|
||||
with mock.patch(
|
||||
"providers.aws.services.rds.rds_snapshots_public_access.rds_snapshots_public_access.rds_client",
|
||||
new=RDS(current_audit_info),
|
||||
) as service_client:
|
||||
# Test Check
|
||||
from providers.aws.services.rds.rds_snapshots_public_access.rds_snapshots_public_access import (
|
||||
rds_snapshots_public_access,
|
||||
)
|
||||
|
||||
service_client.db_snapshots[0].public = True
|
||||
check = rds_snapshots_public_access()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert search(
|
||||
"is public",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == "snapshot-1"
|
||||
|
||||
@mock_rds
|
||||
def test_rds_cluster_private_snapshot(self):
|
||||
conn = client("rds", region_name=AWS_REGION)
|
||||
conn.create_db_cluster(
|
||||
DBClusterIdentifier="db-primary-1",
|
||||
AllocatedStorage=10,
|
||||
Engine="postgres",
|
||||
DBClusterInstanceClass="db.m1.small",
|
||||
MasterUsername="root",
|
||||
MasterUserPassword="hunter2000",
|
||||
)
|
||||
|
||||
conn.create_db_cluster_snapshot(
|
||||
DBClusterIdentifier="db-primary-1", DBClusterSnapshotIdentifier="snapshot-1"
|
||||
)
|
||||
from providers.aws.lib.audit_info.audit_info import current_audit_info
|
||||
from providers.aws.services.rds.rds_service import RDS
|
||||
|
||||
current_audit_info.audited_partition = "aws"
|
||||
|
||||
with mock.patch(
|
||||
"providers.aws.services.rds.rds_snapshots_public_access.rds_snapshots_public_access.rds_client",
|
||||
new=RDS(current_audit_info),
|
||||
):
|
||||
# Test Check
|
||||
from providers.aws.services.rds.rds_snapshots_public_access.rds_snapshots_public_access import (
|
||||
rds_snapshots_public_access,
|
||||
)
|
||||
|
||||
check = rds_snapshots_public_access()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert search(
|
||||
"is not shared",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == "snapshot-1"
|
||||
|
||||
@mock_rds
|
||||
def test_rds_cluster_public_snapshot(self):
|
||||
conn = client("rds", region_name=AWS_REGION)
|
||||
conn.create_db_cluster(
|
||||
DBClusterIdentifier="db-primary-1",
|
||||
AllocatedStorage=10,
|
||||
Engine="postgres",
|
||||
DBClusterInstanceClass="db.m1.small",
|
||||
MasterUsername="root",
|
||||
MasterUserPassword="hunter2000",
|
||||
)
|
||||
|
||||
conn.create_db_cluster_snapshot(
|
||||
DBClusterIdentifier="db-primary-1", DBClusterSnapshotIdentifier="snapshot-1"
|
||||
)
|
||||
from providers.aws.lib.audit_info.audit_info import current_audit_info
|
||||
from providers.aws.services.rds.rds_service import RDS
|
||||
|
||||
current_audit_info.audited_partition = "aws"
|
||||
|
||||
with mock.patch(
|
||||
"providers.aws.services.rds.rds_snapshots_public_access.rds_snapshots_public_access.rds_client",
|
||||
new=RDS(current_audit_info),
|
||||
) as service_client:
|
||||
# Test Check
|
||||
from providers.aws.services.rds.rds_snapshots_public_access.rds_snapshots_public_access import (
|
||||
rds_snapshots_public_access,
|
||||
)
|
||||
|
||||
service_client.db_cluster_snapshots[0].public = True
|
||||
check = rds_snapshots_public_access()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert search(
|
||||
"is public",
|
||||
result[0].status_extended,
|
||||
)
|
||||
assert result[0].resource_id == "snapshot-1"
|
||||
Reference in New Issue
Block a user