mirror of
https://github.com/ghndrx/prowler.git
synced 2026-02-10 14:55:00 +00:00
feat(config): add config service and checks and check43 (#1441)
Co-authored-by: Pepe Fagoaga <pepe@verica.io>
This commit is contained in:
0
providers/aws/services/config/__init__.py
Normal file
0
providers/aws/services/config/__init__.py
Normal file
@@ -1,47 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Prowler - the handy cloud security tool (copyright 2019) by Toni de la Fuente
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
# use this file except in compliance with the License. You may obtain a copy
|
||||
# of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software distributed
|
||||
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations under the License.
|
||||
|
||||
CHECK_ID_check25="2.5"
|
||||
CHECK_TITLE_check25="[check25] Ensure AWS Config is enabled in all regions"
|
||||
CHECK_SCORED_check25="SCORED"
|
||||
CHECK_CIS_LEVEL_check25="LEVEL1"
|
||||
CHECK_SEVERITY_check25="Medium"
|
||||
CHECK_ASFF_TYPE_check25="Software and Configuration Checks/Industry and Regulatory Standards/CIS AWS Foundations Benchmark"
|
||||
CHECK_ALTERNATE_check205="check25"
|
||||
CHECK_ASFF_COMPLIANCE_TYPE_check25="ens-op.exp.1.aws.cfg.1"
|
||||
CHECK_SERVICENAME_check25="config"
|
||||
CHECK_RISK_check25='The AWS configuration item history captured by AWS Config enables security analysis; resource change tracking; and compliance auditing.'
|
||||
CHECK_REMEDIATION_check25='It is recommended to enable AWS Config be enabled in all regions.'
|
||||
CHECK_DOC_check25='https://aws.amazon.com/blogs/mt/aws-config-best-practices/'
|
||||
CHECK_CAF_EPIC_check25='Logging and Monitoring'
|
||||
|
||||
check25(){
|
||||
# "Ensure AWS Config is enabled in all regions (Scored)"
|
||||
for regx in $REGIONS; do
|
||||
CHECK_AWSCONFIG_RECORDING=$($AWSCLI configservice describe-configuration-recorder-status $PROFILE_OPT --region $regx --query 'ConfigurationRecordersStatus[*].recording' --output text 2>&1)
|
||||
CHECK_AWSCONFIG_STATUS=$($AWSCLI configservice describe-configuration-recorder-status $PROFILE_OPT --region $regx --query 'ConfigurationRecordersStatus[*].lastStatus' --output text 2>&1)
|
||||
if [[ $(echo "$CHECK_AWSCONFIG_STATUS" | grep AccessDenied) ]]; then
|
||||
textInfo "$regx: Access Denied trying to describe configuration recorder status" "$regx" "recorder"
|
||||
continue
|
||||
fi
|
||||
if [[ $CHECK_AWSCONFIG_RECORDING == "True" ]]; then
|
||||
if [[ $CHECK_AWSCONFIG_STATUS == "SUCCESS" ]]; then
|
||||
textPass "$regx: AWS Config recorder enabled" "$regx" "recorder"
|
||||
else
|
||||
textFail "$regx: AWS Config recorder in failure state" "$regx" "recorder"
|
||||
fi
|
||||
else
|
||||
textFail "$regx: AWS Config recorder disabled" "$regx" "recorder"
|
||||
fi
|
||||
done
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Prowler - the handy cloud security tool (copyright 2019) by Toni de la Fuente
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
# use this file except in compliance with the License. You may obtain a copy
|
||||
# of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software distributed
|
||||
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations under the License.
|
||||
#
|
||||
# Remediation:
|
||||
#
|
||||
# https://d1.awsstatic.com/whitepapers/compliance/AWS_CIS_Foundations_Benchmark.pdf
|
||||
#
|
||||
# aws logs put-metric-filter \
|
||||
# --region us-east-1 \
|
||||
# --log-group-name CloudTrail/CloudWatchLogGroup \
|
||||
# --filter-name AWSConfigChanges \
|
||||
# --filter-pattern '{ ($.eventSource = config.amazonaws.com) && (($.eventName = StopConfigurationRecorder)||($.eventName = DeleteDeliveryChannel)||($.eventName = PutDeliveryChannel)||($.eventName = PutConfigurationRecorder)) }' \
|
||||
# --metric-transformations metricName=ConfigEventCount,metricNamespace=CloudTrailMetrics,metricValue=1
|
||||
#
|
||||
# aws cloudwatch put-metric-alarm \
|
||||
# --region us-east-1 \
|
||||
# --alarm-name AWSConfigChangesAlarm \
|
||||
# --alarm-description "Triggered by AWS Config changes." \
|
||||
# --metric-name ConfigEventCount \
|
||||
# --namespace CloudTrailMetrics \
|
||||
# --statistic Sum \
|
||||
# --comparison-operator GreaterThanOrEqualToThreshold \
|
||||
# --evaluation-periods 1 \
|
||||
# --period 300 \
|
||||
# --threshold 1 \
|
||||
# --actions-enabled \
|
||||
# --alarm-actions arn:aws:sns:us-east-1:123456789012:CloudWatchAlarmTopic
|
||||
|
||||
CHECK_ID_check39="3.9"
|
||||
CHECK_TITLE_check39="[check39] Ensure a log metric filter and alarm exist for AWS Config configuration changes"
|
||||
CHECK_SCORED_check39="SCORED"
|
||||
CHECK_CIS_LEVEL_check39="LEVEL2"
|
||||
CHECK_SEVERITY_check39="Medium"
|
||||
CHECK_ASFF_TYPE_check39="Software and Configuration Checks/Industry and Regulatory Standards/CIS AWS Foundations Benchmark"
|
||||
CHECK_ASFF_RESOURCE_TYPE_check39="AwsCloudTrailTrail"
|
||||
CHECK_ALTERNATE_check309="check39"
|
||||
CHECK_SERVICENAME_check39="config"
|
||||
CHECK_RISK_check39='If not enabled important changes to accounts could go unnoticed or difficult to find.'
|
||||
CHECK_REMEDIATION_check39='Use this service as a complement to implement detective controls that cannot be prevented. (e.g. a Security Group is modified to open to internet without restrictions or route changed to avoid going thru the network firewall). Ensure AWS Config is enabled in all regions in order to detect any not intended action. On the other hand if sufficient preventive controls to make changes in critical services are in place; the rating on this finding can be lowered or discarded depending on residual risk.'
|
||||
CHECK_DOC_check39='https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudwatch-alarms-for-cloudtrail.html'
|
||||
CHECK_CAF_EPIC_check39='Logging and Monitoring'
|
||||
|
||||
check39(){
|
||||
check3x '\$\.eventSource\s*=\s*config.amazonaws.com.+\$\.eventName\s*=\s*StopConfigurationRecorder.+\$\.eventName\s*=\s*DeleteDeliveryChannel.+\$\.eventName\s*=\s*PutDeliveryChannel.+\$\.eventName\s*=\s*PutConfigurationRecorder'
|
||||
}
|
||||
4
providers/aws/services/config/config_client.py
Normal file
4
providers/aws/services/config/config_client.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from providers.aws.lib.audit_info.audit_info import current_audit_info
|
||||
from providers.aws.services.config.config_service import Config
|
||||
|
||||
config_client = Config(current_audit_info)
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"Provider": "aws",
|
||||
"CheckID": "config_recorder_all_regions_enabled",
|
||||
"CheckTitle": "Ensure AWS Config is enabled in all regions.",
|
||||
"CheckType": ["Logging and Monitoring"],
|
||||
"ServiceName": "config",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "arn:partition:access-recorder:region:account-id:recorder/resource-id",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "Other",
|
||||
"Description": "Ensure AWS Config is enabled in all regions.",
|
||||
"Risk": "The AWS configuration item history captured by AWS Config enables security analysis, resource change tracking and compliance auditing.",
|
||||
"RelatedUrl": "https://aws.amazon.com/blogs/mt/aws-config-best-practices/",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "https://docs.bridgecrew.io/docs/logging_5-enable-aws-config-regions#cli-command",
|
||||
"NativeIaC": "",
|
||||
"Other": "https://docs.bridgecrew.io/docs/logging_5-enable-aws-config-regions#aws-console",
|
||||
"Terraform": "https://docs.bridgecrew.io/docs/logging_5-enable-aws-config-regions#terraform"
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "It is recommended to enable AWS Config be enabled in all regions.",
|
||||
"Url": "https://aws.amazon.com/blogs/mt/aws-config-best-practices/"
|
||||
}
|
||||
},
|
||||
"Categories": [],
|
||||
"Tags": {
|
||||
"Tag1Key": "value",
|
||||
"Tag2Key": "value"
|
||||
},
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": "",
|
||||
"Compliance": []
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
from lib.check.models import Check, Check_Report
|
||||
from providers.aws.services.config.config_client import config_client
|
||||
|
||||
|
||||
class config_recorder_all_regions_enabled(Check):
|
||||
def execute(self):
|
||||
findings = []
|
||||
for recorder in config_client.recorders:
|
||||
report = Check_Report(self.metadata)
|
||||
report.region = recorder.region
|
||||
report.resource_id = recorder.name
|
||||
# Check if Config is enabled in region
|
||||
if not recorder.name:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"No AWS Config recorders in region."
|
||||
else:
|
||||
if recorder.recording:
|
||||
if recorder.last_status == "Failure":
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
f"AWS Config recorder {recorder.name} in failure state."
|
||||
)
|
||||
else:
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
f"AWS Config recorder {recorder.name} is enabled."
|
||||
)
|
||||
else:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
f"AWS Config recorder {recorder.name} is disabled."
|
||||
)
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
@@ -0,0 +1,107 @@
|
||||
from unittest import mock
|
||||
|
||||
from boto3 import client
|
||||
from moto import mock_config
|
||||
|
||||
AWS_REGION = "us-east-1"
|
||||
|
||||
|
||||
class Test_config_recorder_all_regions_enabled:
|
||||
@mock_config
|
||||
def test_config_no_recorders(self):
|
||||
from providers.aws.lib.audit_info.audit_info import current_audit_info
|
||||
from providers.aws.services.config.config_service import Config
|
||||
|
||||
current_audit_info.audited_partition = "aws"
|
||||
|
||||
with mock.patch(
|
||||
"providers.aws.services.config.config_recorder_all_regions_enabled.config_recorder_all_regions_enabled.config_client",
|
||||
new=Config(current_audit_info),
|
||||
):
|
||||
# Test Check
|
||||
from providers.aws.services.config.config_recorder_all_regions_enabled.config_recorder_all_regions_enabled import (
|
||||
config_recorder_all_regions_enabled,
|
||||
)
|
||||
|
||||
check = config_recorder_all_regions_enabled()
|
||||
result = check.execute()
|
||||
|
||||
assert (
|
||||
len(result) == 23
|
||||
) # One fail result per region, since there are no recorders
|
||||
assert result[0].status == "FAIL"
|
||||
|
||||
@mock_config
|
||||
def test_config_one_recoder_disabled(self):
|
||||
# Create Config Mocked Resources
|
||||
config_client = client("config", region_name=AWS_REGION)
|
||||
# Create Config Recorder
|
||||
config_client.put_configuration_recorder(
|
||||
ConfigurationRecorder={"name": "default", "roleARN": "somearn"}
|
||||
)
|
||||
from providers.aws.lib.audit_info.audit_info import current_audit_info
|
||||
from providers.aws.services.config.config_service import Config
|
||||
|
||||
current_audit_info.audited_partition = "aws"
|
||||
|
||||
with mock.patch(
|
||||
"providers.aws.services.config.config_recorder_all_regions_enabled.config_recorder_all_regions_enabled.config_client",
|
||||
new=Config(current_audit_info),
|
||||
):
|
||||
# Test Check
|
||||
from providers.aws.services.config.config_recorder_all_regions_enabled.config_recorder_all_regions_enabled import (
|
||||
config_recorder_all_regions_enabled,
|
||||
)
|
||||
|
||||
check = config_recorder_all_regions_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 23
|
||||
# Search for the recorder just created
|
||||
for recorder in result:
|
||||
if recorder.resource_id:
|
||||
assert recorder.status == "FAIL"
|
||||
assert (
|
||||
recorder.status_extended
|
||||
== f"AWS Config recorder default is disabled."
|
||||
)
|
||||
assert recorder.resource_id == "default"
|
||||
|
||||
@mock_config
|
||||
def test_config_one_recoder_enabled(self):
|
||||
# Create Config Mocked Resources
|
||||
config_client = client("config", region_name=AWS_REGION)
|
||||
# Create Config Recorder and start it
|
||||
config_client.put_configuration_recorder(
|
||||
ConfigurationRecorder={"name": "default", "roleARN": "somearn"}
|
||||
)
|
||||
# Make the delivery channel
|
||||
config_client.put_delivery_channel(
|
||||
DeliveryChannel={"name": "testchannel", "s3BucketName": "somebucket"}
|
||||
)
|
||||
config_client.start_configuration_recorder(ConfigurationRecorderName="default")
|
||||
from providers.aws.lib.audit_info.audit_info import current_audit_info
|
||||
from providers.aws.services.config.config_service import Config
|
||||
|
||||
current_audit_info.audited_partition = "aws"
|
||||
|
||||
with mock.patch(
|
||||
"providers.aws.services.config.config_recorder_all_regions_enabled.config_recorder_all_regions_enabled.config_client",
|
||||
new=Config(current_audit_info),
|
||||
):
|
||||
# Test Check
|
||||
from providers.aws.services.config.config_recorder_all_regions_enabled.config_recorder_all_regions_enabled import (
|
||||
config_recorder_all_regions_enabled,
|
||||
)
|
||||
|
||||
check = config_recorder_all_regions_enabled()
|
||||
result = check.execute()
|
||||
assert len(result) == 23
|
||||
# Search for the recorder just created
|
||||
for recorder in result:
|
||||
if recorder.resource_id:
|
||||
assert recorder.status == "PASS"
|
||||
assert (
|
||||
recorder.status_extended
|
||||
== f"AWS Config recorder default is enabled."
|
||||
)
|
||||
assert recorder.resource_id == "default"
|
||||
90
providers/aws/services/config/config_service.py
Normal file
90
providers/aws/services/config/config_service.py
Normal file
@@ -0,0 +1,90 @@
|
||||
import threading
|
||||
from dataclasses import dataclass
|
||||
|
||||
from lib.logger import logger
|
||||
from providers.aws.aws_provider import generate_regional_clients
|
||||
|
||||
|
||||
################## Config
|
||||
class Config:
|
||||
def __init__(self, audit_info):
|
||||
self.service = "config"
|
||||
self.session = audit_info.audit_session
|
||||
self.audited_account = audit_info.audited_account
|
||||
self.regional_clients = generate_regional_clients(self.service, audit_info)
|
||||
self.recorders = []
|
||||
self.__threading_call__(self.__describe_configuration_recorder_status__)
|
||||
|
||||
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_configuration_recorder_status__(self, regional_client):
|
||||
logger.info("Config - Listing Recorders...")
|
||||
try:
|
||||
recorders = regional_client.describe_configuration_recorder_status()[
|
||||
"ConfigurationRecordersStatus"
|
||||
]
|
||||
if recorders:
|
||||
for recorder in recorders:
|
||||
if "lastStatus" in recorder:
|
||||
self.recorders.append(
|
||||
Recorder(
|
||||
recorder["name"],
|
||||
recorder["recording"],
|
||||
recorder["lastStatus"],
|
||||
regional_client.region,
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.recorders.append(
|
||||
Recorder(
|
||||
recorder["name"],
|
||||
recorder["recording"],
|
||||
None,
|
||||
regional_client.region,
|
||||
)
|
||||
)
|
||||
# No config recorders in region
|
||||
else:
|
||||
self.recorders.append(
|
||||
Recorder(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
regional_client.region,
|
||||
)
|
||||
)
|
||||
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Recorder:
|
||||
name: str
|
||||
recording: bool
|
||||
last_status: str
|
||||
region: str
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
recording,
|
||||
last_status,
|
||||
region,
|
||||
):
|
||||
self.name = name
|
||||
self.recording = recording
|
||||
self.last_status = last_status
|
||||
self.region = region
|
||||
89
providers/aws/services/config/config_service_test.py
Normal file
89
providers/aws/services/config/config_service_test.py
Normal file
@@ -0,0 +1,89 @@
|
||||
from boto3 import client, session
|
||||
from moto import mock_config
|
||||
|
||||
from providers.aws.lib.audit_info.models import AWS_Audit_Info
|
||||
from providers.aws.services.config.config_service import Config
|
||||
|
||||
AWS_ACCOUNT_NUMBER = 123456789012
|
||||
AWS_REGION = "us-east-1"
|
||||
|
||||
|
||||
class Test_Config_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 Config Service
|
||||
@mock_config
|
||||
def test_service(self):
|
||||
# Config client for this test class
|
||||
audit_info = self.set_mocked_audit_info()
|
||||
config = Config(audit_info)
|
||||
assert config.service == "config"
|
||||
|
||||
# Test Config Client
|
||||
@mock_config
|
||||
def test_client(self):
|
||||
# Config client for this test class
|
||||
audit_info = self.set_mocked_audit_info()
|
||||
config = Config(audit_info)
|
||||
for client in config.regional_clients.values():
|
||||
assert client.__class__.__name__ == "ConfigService"
|
||||
|
||||
# Test Config Session
|
||||
@mock_config
|
||||
def test__get_session__(self):
|
||||
# Config client for this test class
|
||||
audit_info = self.set_mocked_audit_info()
|
||||
config = Config(audit_info)
|
||||
assert config.session.__class__.__name__ == "Session"
|
||||
|
||||
# Test Config Session
|
||||
@mock_config
|
||||
def test_audited_account(self):
|
||||
# Config client for this test class
|
||||
audit_info = self.set_mocked_audit_info()
|
||||
config = Config(audit_info)
|
||||
assert config.audited_account == AWS_ACCOUNT_NUMBER
|
||||
|
||||
# Test Config Get Rest APIs
|
||||
@mock_config
|
||||
def test__describe_configuration_recorder_status__(self):
|
||||
# Generate Config Client
|
||||
config_client = client("config", region_name=AWS_REGION)
|
||||
# Create Config Recorder and start it
|
||||
config_client.put_configuration_recorder(
|
||||
ConfigurationRecorder={"name": "default", "roleARN": "somearn"}
|
||||
)
|
||||
# Make the delivery channel
|
||||
config_client.put_delivery_channel(
|
||||
DeliveryChannel={"name": "testchannel", "s3BucketName": "somebucket"}
|
||||
)
|
||||
config_client.start_configuration_recorder(ConfigurationRecorderName="default")
|
||||
# Config client for this test class
|
||||
audit_info = self.set_mocked_audit_info()
|
||||
config = Config(audit_info)
|
||||
# One recorder per region
|
||||
assert len(config.recorders) == 23
|
||||
# Check the active one
|
||||
# Search for the recorder just created
|
||||
for recorder in config.recorders:
|
||||
if recorder.name == "default":
|
||||
assert recorder.recording == True
|
||||
Reference in New Issue
Block a user