aws-cis-controls-assessment 1.1.3__py3-none-any.whl → 1.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aws_cis_assessment/__init__.py +4 -4
- aws_cis_assessment/config/rules/cis_controls_ig1.yaml +365 -2
- aws_cis_assessment/controls/ig1/control_access_analyzer.py +198 -0
- aws_cis_assessment/controls/ig1/control_access_asset_mgmt.py +360 -0
- aws_cis_assessment/controls/ig1/control_access_control.py +323 -0
- aws_cis_assessment/controls/ig1/control_backup_security.py +579 -0
- aws_cis_assessment/controls/ig1/control_cloudfront_logging.py +215 -0
- aws_cis_assessment/controls/ig1/control_configuration_mgmt.py +407 -0
- aws_cis_assessment/controls/ig1/control_data_classification.py +255 -0
- aws_cis_assessment/controls/ig1/control_dynamodb_encryption.py +279 -0
- aws_cis_assessment/controls/ig1/control_ebs_encryption.py +177 -0
- aws_cis_assessment/controls/ig1/control_efs_encryption.py +243 -0
- aws_cis_assessment/controls/ig1/control_elb_logging.py +195 -0
- aws_cis_assessment/controls/ig1/control_guardduty.py +156 -0
- aws_cis_assessment/controls/ig1/control_inspector.py +184 -0
- aws_cis_assessment/controls/ig1/control_inventory.py +511 -0
- aws_cis_assessment/controls/ig1/control_macie.py +165 -0
- aws_cis_assessment/controls/ig1/control_messaging_encryption.py +419 -0
- aws_cis_assessment/controls/ig1/control_mfa.py +485 -0
- aws_cis_assessment/controls/ig1/control_network_security.py +194 -619
- aws_cis_assessment/controls/ig1/control_patch_management.py +626 -0
- aws_cis_assessment/controls/ig1/control_rds_encryption.py +228 -0
- aws_cis_assessment/controls/ig1/control_s3_encryption.py +383 -0
- aws_cis_assessment/controls/ig1/control_tls_ssl.py +556 -0
- aws_cis_assessment/controls/ig1/control_version_mgmt.py +329 -0
- aws_cis_assessment/controls/ig1/control_vpc_flow_logs.py +205 -0
- aws_cis_assessment/controls/ig1/control_waf_logging.py +226 -0
- aws_cis_assessment/core/models.py +20 -1
- aws_cis_assessment/core/scoring_engine.py +98 -1
- aws_cis_assessment/reporters/base_reporter.py +31 -1
- aws_cis_assessment/reporters/html_reporter.py +172 -11
- aws_cis_controls_assessment-1.2.0.dist-info/METADATA +320 -0
- {aws_cis_controls_assessment-1.1.3.dist-info → aws_cis_controls_assessment-1.2.0.dist-info}/RECORD +39 -15
- docs/developer-guide.md +204 -5
- docs/user-guide.md +137 -4
- aws_cis_controls_assessment-1.1.3.dist-info/METADATA +0 -404
- {aws_cis_controls_assessment-1.1.3.dist-info → aws_cis_controls_assessment-1.2.0.dist-info}/WHEEL +0 -0
- {aws_cis_controls_assessment-1.1.3.dist-info → aws_cis_controls_assessment-1.2.0.dist-info}/entry_points.txt +0 -0
- {aws_cis_controls_assessment-1.1.3.dist-info → aws_cis_controls_assessment-1.2.0.dist-info}/licenses/LICENSE +0 -0
- {aws_cis_controls_assessment-1.1.3.dist-info → aws_cis_controls_assessment-1.2.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CIS Control 8.2 - CloudFront Access Logging
|
|
3
|
+
Ensures CloudFront distributions have access logging enabled.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
from typing import List, Dict, Any
|
|
8
|
+
from botocore.exceptions import ClientError
|
|
9
|
+
|
|
10
|
+
from aws_cis_assessment.controls.base_control import BaseConfigRuleAssessment
|
|
11
|
+
from aws_cis_assessment.core.models import ComplianceResult, ComplianceStatus
|
|
12
|
+
from aws_cis_assessment.core.aws_client_factory import AWSClientFactory
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class CloudFrontLoggingEnabledAssessment(BaseConfigRuleAssessment):
|
|
18
|
+
"""
|
|
19
|
+
CIS Control 8.2 - Collect Audit Logs
|
|
20
|
+
AWS Config Rule: cloudfront-logging-enabled
|
|
21
|
+
|
|
22
|
+
Ensures CloudFront distributions have access logging enabled.
|
|
23
|
+
Access logs contain detailed information about every request made to the distribution,
|
|
24
|
+
essential for security analysis, troubleshooting, and compliance.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self):
|
|
28
|
+
super().__init__(
|
|
29
|
+
rule_name="cloudfront-logging-enabled",
|
|
30
|
+
control_id="8.2",
|
|
31
|
+
resource_types=["AWS::CloudFront::Distribution"]
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
|
|
35
|
+
"""Get CloudFront distributions and their logging configuration."""
|
|
36
|
+
if resource_type != "AWS::CloudFront::Distribution":
|
|
37
|
+
return []
|
|
38
|
+
|
|
39
|
+
# CloudFront is a global service, only query from us-east-1
|
|
40
|
+
if region != 'us-east-1':
|
|
41
|
+
return []
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
cloudfront_client = aws_factory.get_client('cloudfront', 'us-east-1')
|
|
45
|
+
|
|
46
|
+
# List all distributions
|
|
47
|
+
response = cloudfront_client.list_distributions()
|
|
48
|
+
distribution_list = response.get('DistributionList', {})
|
|
49
|
+
distributions = distribution_list.get('Items', [])
|
|
50
|
+
|
|
51
|
+
if not distributions:
|
|
52
|
+
return []
|
|
53
|
+
|
|
54
|
+
# Get detailed configuration for each distribution
|
|
55
|
+
dist_resources = []
|
|
56
|
+
for dist in distributions:
|
|
57
|
+
dist_id = dist.get('Id', '')
|
|
58
|
+
dist_arn = dist.get('ARN', '')
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
# Get full distribution configuration
|
|
62
|
+
dist_response = cloudfront_client.get_distribution(Id=dist_id)
|
|
63
|
+
dist_config = dist_response.get('Distribution', {}).get('DistributionConfig', {})
|
|
64
|
+
|
|
65
|
+
# Check logging configuration
|
|
66
|
+
logging_config = dist_config.get('Logging', {})
|
|
67
|
+
logging_enabled = logging_config.get('Enabled', False)
|
|
68
|
+
s3_bucket = logging_config.get('Bucket', '')
|
|
69
|
+
log_prefix = logging_config.get('Prefix', '')
|
|
70
|
+
|
|
71
|
+
dist_resources.append({
|
|
72
|
+
'DistributionId': dist_id,
|
|
73
|
+
'DistributionArn': dist_arn,
|
|
74
|
+
'DomainName': dist.get('DomainName', ''),
|
|
75
|
+
'Status': dist.get('Status', ''),
|
|
76
|
+
'Enabled': dist.get('Enabled', False),
|
|
77
|
+
'Region': 'global', # CloudFront is global
|
|
78
|
+
'LoggingEnabled': logging_enabled,
|
|
79
|
+
'S3Bucket': s3_bucket,
|
|
80
|
+
'LogPrefix': log_prefix,
|
|
81
|
+
'Comment': dist_config.get('Comment', '')
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
except ClientError as e:
|
|
85
|
+
logger.warning(f"Error getting distribution {dist_id} details: {e}")
|
|
86
|
+
continue
|
|
87
|
+
|
|
88
|
+
return dist_resources
|
|
89
|
+
|
|
90
|
+
except ClientError as e:
|
|
91
|
+
error_code = e.response.get('Error', {}).get('Code', '')
|
|
92
|
+
if error_code == 'AccessDenied':
|
|
93
|
+
logger.warning(f"Access denied to CloudFront")
|
|
94
|
+
else:
|
|
95
|
+
logger.error(f"Error listing CloudFront distributions: {e}")
|
|
96
|
+
return []
|
|
97
|
+
|
|
98
|
+
def _evaluate_resource_compliance(
|
|
99
|
+
self,
|
|
100
|
+
resource: Dict[str, Any],
|
|
101
|
+
aws_factory: AWSClientFactory,
|
|
102
|
+
region: str
|
|
103
|
+
) -> ComplianceResult:
|
|
104
|
+
"""Evaluate if CloudFront distribution has logging enabled."""
|
|
105
|
+
dist_id = resource.get('DistributionId', '')
|
|
106
|
+
dist_arn = resource.get('DistributionArn', '')
|
|
107
|
+
domain_name = resource.get('DomainName', '')
|
|
108
|
+
logging_enabled = resource.get('LoggingEnabled', False)
|
|
109
|
+
s3_bucket = resource.get('S3Bucket', '')
|
|
110
|
+
dist_enabled = resource.get('Enabled', False)
|
|
111
|
+
|
|
112
|
+
# Check if logging is enabled
|
|
113
|
+
is_compliant = logging_enabled and s3_bucket
|
|
114
|
+
|
|
115
|
+
if is_compliant:
|
|
116
|
+
evaluation_reason = (
|
|
117
|
+
f"CloudFront distribution {dist_id} ({domain_name}) has access logging enabled. "
|
|
118
|
+
f"Logs are stored in S3 bucket: {s3_bucket}"
|
|
119
|
+
)
|
|
120
|
+
compliance_status = ComplianceStatus.COMPLIANT
|
|
121
|
+
else:
|
|
122
|
+
if not dist_enabled:
|
|
123
|
+
evaluation_reason = f"CloudFront distribution {dist_id} is disabled."
|
|
124
|
+
compliance_status = ComplianceStatus.NOT_APPLICABLE
|
|
125
|
+
elif not logging_enabled:
|
|
126
|
+
evaluation_reason = f"CloudFront distribution {dist_id} ({domain_name}) does not have access logging enabled."
|
|
127
|
+
compliance_status = ComplianceStatus.NON_COMPLIANT
|
|
128
|
+
else:
|
|
129
|
+
evaluation_reason = f"CloudFront distribution {dist_id} ({domain_name}) has logging enabled but no S3 bucket configured."
|
|
130
|
+
compliance_status = ComplianceStatus.NON_COMPLIANT
|
|
131
|
+
|
|
132
|
+
return ComplianceResult(
|
|
133
|
+
resource_id=dist_arn,
|
|
134
|
+
resource_type="AWS::CloudFront::Distribution",
|
|
135
|
+
compliance_status=compliance_status,
|
|
136
|
+
evaluation_reason=evaluation_reason,
|
|
137
|
+
config_rule_name=self.rule_name,
|
|
138
|
+
region='global'
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
def _get_rule_remediation_steps(self) -> List[str]:
|
|
142
|
+
"""Get remediation steps for enabling CloudFront access logging."""
|
|
143
|
+
return [
|
|
144
|
+
"1. Enable CloudFront access logging in the AWS Console:",
|
|
145
|
+
" - Navigate to CloudFront service",
|
|
146
|
+
" - Select the distribution",
|
|
147
|
+
" - Click 'Edit'",
|
|
148
|
+
" - Under 'Standard logging', select 'On'",
|
|
149
|
+
" - Specify S3 bucket for logs (must be in same account)",
|
|
150
|
+
" - Optionally specify log prefix",
|
|
151
|
+
" - Click 'Save changes'",
|
|
152
|
+
"",
|
|
153
|
+
"2. Create S3 bucket for logs (if needed):",
|
|
154
|
+
" aws s3 mb s3://<bucket-name> --region us-east-1",
|
|
155
|
+
"",
|
|
156
|
+
"3. Add bucket policy to allow CloudFront to write logs:",
|
|
157
|
+
" {",
|
|
158
|
+
' "Version": "2012-10-17",',
|
|
159
|
+
' "Statement": [{',
|
|
160
|
+
' "Effect": "Allow",',
|
|
161
|
+
' "Principal": {"Service": "cloudfront.amazonaws.com"},',
|
|
162
|
+
' "Action": "s3:PutObject",',
|
|
163
|
+
' "Resource": "arn:aws:s3:::<bucket-name>/*",',
|
|
164
|
+
' "Condition": {',
|
|
165
|
+
' "StringEquals": {',
|
|
166
|
+
' "AWS:SourceAccount": "<account-id>"',
|
|
167
|
+
" }",
|
|
168
|
+
" }",
|
|
169
|
+
" }]",
|
|
170
|
+
" }",
|
|
171
|
+
"",
|
|
172
|
+
"4. Enable access logging using AWS CLI:",
|
|
173
|
+
" # First, get current distribution config",
|
|
174
|
+
" aws cloudfront get-distribution-config \\",
|
|
175
|
+
" --id <distribution-id> > dist-config.json",
|
|
176
|
+
"",
|
|
177
|
+
" # Edit dist-config.json to add logging:",
|
|
178
|
+
' "Logging": {',
|
|
179
|
+
' "Enabled": true,',
|
|
180
|
+
' "IncludeCookies": false,',
|
|
181
|
+
' "Bucket": "<bucket-name>.s3.amazonaws.com",',
|
|
182
|
+
' "Prefix": "cloudfront-logs/"',
|
|
183
|
+
" }",
|
|
184
|
+
"",
|
|
185
|
+
" # Update distribution",
|
|
186
|
+
" aws cloudfront update-distribution \\",
|
|
187
|
+
" --id <distribution-id> \\",
|
|
188
|
+
" --distribution-config file://dist-config.json \\",
|
|
189
|
+
" --if-match <etag>",
|
|
190
|
+
"",
|
|
191
|
+
"5. Best practices:",
|
|
192
|
+
" - Use a dedicated S3 bucket for logs",
|
|
193
|
+
" - Enable S3 bucket encryption",
|
|
194
|
+
" - Set lifecycle policies (30-90 days retention)",
|
|
195
|
+
" - Use S3 Intelligent-Tiering for cost optimization",
|
|
196
|
+
" - Enable for all distributions",
|
|
197
|
+
" - Include cookies in logs if needed for analysis",
|
|
198
|
+
"",
|
|
199
|
+
"6. Analyze access logs:",
|
|
200
|
+
" - Use Athena to query logs",
|
|
201
|
+
" - Look for:",
|
|
202
|
+
" * Geographic distribution of requests",
|
|
203
|
+
" * Most requested content",
|
|
204
|
+
" * Error rates (4xx, 5xx)",
|
|
205
|
+
" * Potential DDoS attacks",
|
|
206
|
+
" * Cache hit/miss ratios",
|
|
207
|
+
"",
|
|
208
|
+
"7. Note: CloudFront logs may take up to 24 hours to appear",
|
|
209
|
+
"",
|
|
210
|
+
"Priority: HIGH - CloudFront logs are essential for security and performance analysis",
|
|
211
|
+
"Effort: Low - Can be enabled in minutes per distribution",
|
|
212
|
+
"",
|
|
213
|
+
"AWS Documentation:",
|
|
214
|
+
"https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/AccessLogs.html"
|
|
215
|
+
]
|
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CIS Control 4.1 - Configuration Management
|
|
3
|
+
Ensures proper configuration management practices across AWS resources.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
from typing import List, Dict, Any
|
|
8
|
+
from botocore.exceptions import ClientError
|
|
9
|
+
|
|
10
|
+
from aws_cis_assessment.controls.base_control import BaseConfigRuleAssessment
|
|
11
|
+
from aws_cis_assessment.core.models import ComplianceResult, ComplianceStatus
|
|
12
|
+
from aws_cis_assessment.core.aws_client_factory import AWSClientFactory
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ConfigConformancePackDeployedAssessment(BaseConfigRuleAssessment):
|
|
18
|
+
"""
|
|
19
|
+
CIS Control 4.1 - Establish and Maintain a Secure Configuration Process
|
|
20
|
+
AWS Config Rule: config-conformance-pack-deployed
|
|
21
|
+
|
|
22
|
+
Ensures AWS Config conformance packs are deployed for configuration management.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self):
|
|
26
|
+
super().__init__(
|
|
27
|
+
rule_name="config-conformance-pack-deployed",
|
|
28
|
+
control_id="4.1",
|
|
29
|
+
resource_types=["AWS::::Account"]
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
|
|
33
|
+
"""Check if conformance packs are deployed."""
|
|
34
|
+
if resource_type != "AWS::::Account":
|
|
35
|
+
return []
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
config_client = aws_factory.get_client('config', region)
|
|
39
|
+
|
|
40
|
+
response = config_client.describe_conformance_packs()
|
|
41
|
+
packs = response.get('ConformancePackDetails', [])
|
|
42
|
+
|
|
43
|
+
return [{
|
|
44
|
+
'AccountId': region,
|
|
45
|
+
'ConformancePacksDeployed': len(packs) > 0,
|
|
46
|
+
'PackCount': len(packs),
|
|
47
|
+
'PackNames': [p.get('ConformancePackName') for p in packs]
|
|
48
|
+
}]
|
|
49
|
+
|
|
50
|
+
except ClientError as e:
|
|
51
|
+
logger.error(f"Error checking conformance packs in {region}: {e}")
|
|
52
|
+
return []
|
|
53
|
+
|
|
54
|
+
def _evaluate_resource_compliance(
|
|
55
|
+
self,
|
|
56
|
+
resource: Dict[str, Any],
|
|
57
|
+
aws_factory: AWSClientFactory,
|
|
58
|
+
region: str
|
|
59
|
+
) -> ComplianceResult:
|
|
60
|
+
"""Evaluate if conformance packs are deployed."""
|
|
61
|
+
packs_deployed = resource.get('ConformancePacksDeployed', False)
|
|
62
|
+
pack_count = resource.get('PackCount', 0)
|
|
63
|
+
|
|
64
|
+
if packs_deployed:
|
|
65
|
+
evaluation_reason = f"{pack_count} conformance pack(s) deployed"
|
|
66
|
+
compliance_status = ComplianceStatus.COMPLIANT
|
|
67
|
+
else:
|
|
68
|
+
evaluation_reason = "No conformance packs deployed"
|
|
69
|
+
compliance_status = ComplianceStatus.NON_COMPLIANT
|
|
70
|
+
|
|
71
|
+
return ComplianceResult(
|
|
72
|
+
resource_id=f"account-{region}",
|
|
73
|
+
resource_type="AWS::::Account",
|
|
74
|
+
compliance_status=compliance_status,
|
|
75
|
+
evaluation_reason=evaluation_reason,
|
|
76
|
+
config_rule_name=self.rule_name,
|
|
77
|
+
region=region
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
def _get_rule_remediation_steps(self) -> List[str]:
|
|
81
|
+
"""Get remediation steps for deploying conformance packs."""
|
|
82
|
+
return [
|
|
83
|
+
"1. Deploy a conformance pack:",
|
|
84
|
+
" aws configservice put-conformance-pack \\",
|
|
85
|
+
" --conformance-pack-name security-best-practices \\",
|
|
86
|
+
" --template-s3-uri s3://bucket/conformance-pack.yaml",
|
|
87
|
+
"",
|
|
88
|
+
"2. Deploy AWS managed conformance pack:",
|
|
89
|
+
" aws configservice put-conformance-pack \\",
|
|
90
|
+
" --conformance-pack-name operational-best-practices-for-cis",
|
|
91
|
+
"",
|
|
92
|
+
"Priority: HIGH - Essential for configuration compliance",
|
|
93
|
+
"Effort: Medium - Requires pack configuration",
|
|
94
|
+
"",
|
|
95
|
+
"AWS Documentation:",
|
|
96
|
+
"https://docs.aws.amazon.com/config/latest/developerguide/conformance-packs.html"
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class SecurityHubStandardsEnabledAssessment(BaseConfigRuleAssessment):
|
|
101
|
+
"""
|
|
102
|
+
CIS Control 4.1 - Establish and Maintain a Secure Configuration Process
|
|
103
|
+
AWS Config Rule: securityhub-standards-enabled
|
|
104
|
+
|
|
105
|
+
Ensures Security Hub standards are enabled for security configuration management.
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
def __init__(self):
|
|
109
|
+
super().__init__(
|
|
110
|
+
rule_name="securityhub-standards-enabled",
|
|
111
|
+
control_id="4.1",
|
|
112
|
+
resource_types=["AWS::::Account"]
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
|
|
116
|
+
"""Check if Security Hub standards are enabled."""
|
|
117
|
+
if resource_type != "AWS::::Account":
|
|
118
|
+
return []
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
securityhub_client = aws_factory.get_client('securityhub', region)
|
|
122
|
+
|
|
123
|
+
response = securityhub_client.get_enabled_standards()
|
|
124
|
+
standards = response.get('StandardsSubscriptions', [])
|
|
125
|
+
|
|
126
|
+
enabled_standards = [s for s in standards if s.get('StandardsStatus') == 'READY']
|
|
127
|
+
|
|
128
|
+
return [{
|
|
129
|
+
'AccountId': region,
|
|
130
|
+
'StandardsEnabled': len(enabled_standards) > 0,
|
|
131
|
+
'StandardCount': len(enabled_standards),
|
|
132
|
+
'Standards': [s.get('StandardsArn') for s in enabled_standards]
|
|
133
|
+
}]
|
|
134
|
+
|
|
135
|
+
except ClientError as e:
|
|
136
|
+
if e.response.get('Error', {}).get('Code') == 'InvalidAccessException':
|
|
137
|
+
# Security Hub not enabled
|
|
138
|
+
return [{
|
|
139
|
+
'AccountId': region,
|
|
140
|
+
'StandardsEnabled': False,
|
|
141
|
+
'StandardCount': 0,
|
|
142
|
+
'Standards': []
|
|
143
|
+
}]
|
|
144
|
+
logger.error(f"Error checking Security Hub standards in {region}: {e}")
|
|
145
|
+
return []
|
|
146
|
+
|
|
147
|
+
def _evaluate_resource_compliance(
|
|
148
|
+
self,
|
|
149
|
+
resource: Dict[str, Any],
|
|
150
|
+
aws_factory: AWSClientFactory,
|
|
151
|
+
region: str
|
|
152
|
+
) -> ComplianceResult:
|
|
153
|
+
"""Evaluate if Security Hub standards are enabled."""
|
|
154
|
+
standards_enabled = resource.get('StandardsEnabled', False)
|
|
155
|
+
standard_count = resource.get('StandardCount', 0)
|
|
156
|
+
|
|
157
|
+
if standards_enabled:
|
|
158
|
+
evaluation_reason = f"{standard_count} Security Hub standard(s) enabled"
|
|
159
|
+
compliance_status = ComplianceStatus.COMPLIANT
|
|
160
|
+
else:
|
|
161
|
+
evaluation_reason = "No Security Hub standards enabled"
|
|
162
|
+
compliance_status = ComplianceStatus.NON_COMPLIANT
|
|
163
|
+
|
|
164
|
+
return ComplianceResult(
|
|
165
|
+
resource_id=f"account-{region}",
|
|
166
|
+
resource_type="AWS::::Account",
|
|
167
|
+
compliance_status=compliance_status,
|
|
168
|
+
evaluation_reason=evaluation_reason,
|
|
169
|
+
config_rule_name=self.rule_name,
|
|
170
|
+
region=region
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
def _get_rule_remediation_steps(self) -> List[str]:
|
|
174
|
+
"""Get remediation steps for enabling Security Hub standards."""
|
|
175
|
+
return [
|
|
176
|
+
"1. Enable Security Hub:",
|
|
177
|
+
" aws securityhub enable-security-hub",
|
|
178
|
+
"",
|
|
179
|
+
"2. Enable CIS AWS Foundations Benchmark:",
|
|
180
|
+
" aws securityhub batch-enable-standards \\",
|
|
181
|
+
" --standards-subscription-requests StandardsArn=arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0",
|
|
182
|
+
"",
|
|
183
|
+
"3. Enable AWS Foundational Security Best Practices:",
|
|
184
|
+
" aws securityhub batch-enable-standards \\",
|
|
185
|
+
" --standards-subscription-requests StandardsArn=arn:aws:securityhub:us-east-1::standards/aws-foundational-security-best-practices/v/1.0.0",
|
|
186
|
+
"",
|
|
187
|
+
"Priority: HIGH - Critical for security posture",
|
|
188
|
+
"Effort: Low - Quick enablement",
|
|
189
|
+
"",
|
|
190
|
+
"AWS Documentation:",
|
|
191
|
+
"https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-standards.html"
|
|
192
|
+
]
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class AssetTaggingComplianceAssessment(BaseConfigRuleAssessment):
|
|
196
|
+
"""
|
|
197
|
+
CIS Control 1.1 - Establish and Maintain Detailed Enterprise Asset Inventory
|
|
198
|
+
AWS Config Rule: asset-tagging-compliance
|
|
199
|
+
|
|
200
|
+
Ensures resources have required tags for asset management.
|
|
201
|
+
"""
|
|
202
|
+
|
|
203
|
+
def __init__(self):
|
|
204
|
+
super().__init__(
|
|
205
|
+
rule_name="asset-tagging-compliance",
|
|
206
|
+
control_id="1.1",
|
|
207
|
+
resource_types=["AWS::EC2::Instance"]
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
|
|
211
|
+
"""Get EC2 instances and check for required tags."""
|
|
212
|
+
if resource_type != "AWS::EC2::Instance":
|
|
213
|
+
return []
|
|
214
|
+
|
|
215
|
+
try:
|
|
216
|
+
ec2_client = aws_factory.get_client('ec2', region)
|
|
217
|
+
|
|
218
|
+
response = ec2_client.describe_instances()
|
|
219
|
+
instances = []
|
|
220
|
+
|
|
221
|
+
for reservation in response.get('Reservations', []):
|
|
222
|
+
for instance in reservation.get('Instances', []):
|
|
223
|
+
tags = {tag['Key']: tag['Value'] for tag in instance.get('Tags', [])}
|
|
224
|
+
|
|
225
|
+
required_tags = ['Name', 'Environment', 'Owner']
|
|
226
|
+
has_required_tags = all(tag in tags for tag in required_tags)
|
|
227
|
+
|
|
228
|
+
instances.append({
|
|
229
|
+
'InstanceId': instance.get('InstanceId'),
|
|
230
|
+
'Tags': tags,
|
|
231
|
+
'HasRequiredTags': has_required_tags,
|
|
232
|
+
'MissingTags': [tag for tag in required_tags if tag not in tags]
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
return instances
|
|
236
|
+
|
|
237
|
+
except ClientError as e:
|
|
238
|
+
logger.error(f"Error retrieving EC2 instances in {region}: {e}")
|
|
239
|
+
return []
|
|
240
|
+
|
|
241
|
+
def _evaluate_resource_compliance(
|
|
242
|
+
self,
|
|
243
|
+
resource: Dict[str, Any],
|
|
244
|
+
aws_factory: AWSClientFactory,
|
|
245
|
+
region: str
|
|
246
|
+
) -> ComplianceResult:
|
|
247
|
+
"""Evaluate if instance has required tags."""
|
|
248
|
+
instance_id = resource.get('InstanceId', 'unknown')
|
|
249
|
+
has_required_tags = resource.get('HasRequiredTags', False)
|
|
250
|
+
missing_tags = resource.get('MissingTags', [])
|
|
251
|
+
|
|
252
|
+
if has_required_tags:
|
|
253
|
+
evaluation_reason = f"Instance {instance_id} has all required tags"
|
|
254
|
+
compliance_status = ComplianceStatus.COMPLIANT
|
|
255
|
+
else:
|
|
256
|
+
evaluation_reason = f"Instance {instance_id} missing tags: {', '.join(missing_tags)}"
|
|
257
|
+
compliance_status = ComplianceStatus.NON_COMPLIANT
|
|
258
|
+
|
|
259
|
+
return ComplianceResult(
|
|
260
|
+
resource_id=instance_id,
|
|
261
|
+
resource_type="AWS::EC2::Instance",
|
|
262
|
+
compliance_status=compliance_status,
|
|
263
|
+
evaluation_reason=evaluation_reason,
|
|
264
|
+
config_rule_name=self.rule_name,
|
|
265
|
+
region=region
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
def _get_rule_remediation_steps(self) -> List[str]:
|
|
269
|
+
"""Get remediation steps for asset tagging."""
|
|
270
|
+
return [
|
|
271
|
+
"1. Tag an EC2 instance:",
|
|
272
|
+
" aws ec2 create-tags \\",
|
|
273
|
+
" --resources <instance-id> \\",
|
|
274
|
+
" --tags Key=Name,Value=web-server \\",
|
|
275
|
+
" Key=Environment,Value=production \\",
|
|
276
|
+
" Key=Owner,Value=team-name",
|
|
277
|
+
"",
|
|
278
|
+
"2. Tag multiple instances:",
|
|
279
|
+
" for instance in $(aws ec2 describe-instances --query 'Reservations[].Instances[].InstanceId' --output text); do",
|
|
280
|
+
" aws ec2 create-tags --resources $instance --tags Key=Environment,Value=production",
|
|
281
|
+
" done",
|
|
282
|
+
"",
|
|
283
|
+
"Priority: MEDIUM - Important for asset management",
|
|
284
|
+
"Effort: Low - Simple tagging",
|
|
285
|
+
"",
|
|
286
|
+
"AWS Documentation:",
|
|
287
|
+
"https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Using_Tags.html"
|
|
288
|
+
]
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
class InspectorAssessmentEnabledAssessment(BaseConfigRuleAssessment):
|
|
292
|
+
"""
|
|
293
|
+
CIS Control 7.5 - Perform Automated Vulnerability Scans
|
|
294
|
+
AWS Config Rule: inspector-assessment-enabled
|
|
295
|
+
|
|
296
|
+
Ensures Amazon Inspector assessments are actively running.
|
|
297
|
+
"""
|
|
298
|
+
|
|
299
|
+
def __init__(self):
|
|
300
|
+
super().__init__(
|
|
301
|
+
rule_name="inspector-assessment-enabled",
|
|
302
|
+
control_id="7.5",
|
|
303
|
+
resource_types=["AWS::::Account"]
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
def _get_resources(self, aws_factory: AWSClientFactory, resource_type: str, region: str) -> List[Dict[str, Any]]:
|
|
307
|
+
"""Check if Inspector assessments are enabled."""
|
|
308
|
+
if resource_type != "AWS::::Account":
|
|
309
|
+
return []
|
|
310
|
+
|
|
311
|
+
try:
|
|
312
|
+
inspector_client = aws_factory.get_client('inspector2', region)
|
|
313
|
+
|
|
314
|
+
# Check Inspector status
|
|
315
|
+
response = inspector_client.batch_get_account_status(accountIds=[])
|
|
316
|
+
accounts = response.get('accounts', [])
|
|
317
|
+
|
|
318
|
+
if accounts:
|
|
319
|
+
account = accounts[0]
|
|
320
|
+
status = account.get('status', 'DISABLED')
|
|
321
|
+
resource_state = account.get('resourceState', {})
|
|
322
|
+
|
|
323
|
+
ec2_enabled = resource_state.get('ec2', {}).get('status') == 'ENABLED'
|
|
324
|
+
ecr_enabled = resource_state.get('ecr', {}).get('status') == 'ENABLED'
|
|
325
|
+
lambda_enabled = resource_state.get('lambda', {}).get('status') == 'ENABLED'
|
|
326
|
+
|
|
327
|
+
return [{
|
|
328
|
+
'AccountId': region,
|
|
329
|
+
'InspectorEnabled': status == 'ENABLED',
|
|
330
|
+
'EC2Enabled': ec2_enabled,
|
|
331
|
+
'ECREnabled': ecr_enabled,
|
|
332
|
+
'LambdaEnabled': lambda_enabled
|
|
333
|
+
}]
|
|
334
|
+
|
|
335
|
+
return [{
|
|
336
|
+
'AccountId': region,
|
|
337
|
+
'InspectorEnabled': False,
|
|
338
|
+
'EC2Enabled': False,
|
|
339
|
+
'ECREnabled': False,
|
|
340
|
+
'LambdaEnabled': False
|
|
341
|
+
}]
|
|
342
|
+
|
|
343
|
+
except ClientError as e:
|
|
344
|
+
logger.error(f"Error checking Inspector in {region}: {e}")
|
|
345
|
+
return []
|
|
346
|
+
|
|
347
|
+
def _evaluate_resource_compliance(
|
|
348
|
+
self,
|
|
349
|
+
resource: Dict[str, Any],
|
|
350
|
+
aws_factory: AWSClientFactory,
|
|
351
|
+
region: str
|
|
352
|
+
) -> ComplianceResult:
|
|
353
|
+
"""Evaluate if Inspector assessments are enabled."""
|
|
354
|
+
inspector_enabled = resource.get('InspectorEnabled', False)
|
|
355
|
+
ec2_enabled = resource.get('EC2Enabled', False)
|
|
356
|
+
ecr_enabled = resource.get('ECREnabled', False)
|
|
357
|
+
lambda_enabled = resource.get('LambdaEnabled', False)
|
|
358
|
+
|
|
359
|
+
if inspector_enabled and (ec2_enabled or ecr_enabled or lambda_enabled):
|
|
360
|
+
enabled_types = []
|
|
361
|
+
if ec2_enabled:
|
|
362
|
+
enabled_types.append('EC2')
|
|
363
|
+
if ecr_enabled:
|
|
364
|
+
enabled_types.append('ECR')
|
|
365
|
+
if lambda_enabled:
|
|
366
|
+
enabled_types.append('Lambda')
|
|
367
|
+
|
|
368
|
+
evaluation_reason = f"Inspector enabled for: {', '.join(enabled_types)}"
|
|
369
|
+
compliance_status = ComplianceStatus.COMPLIANT
|
|
370
|
+
else:
|
|
371
|
+
evaluation_reason = "Inspector is not enabled or no resource types are being scanned"
|
|
372
|
+
compliance_status = ComplianceStatus.NON_COMPLIANT
|
|
373
|
+
|
|
374
|
+
return ComplianceResult(
|
|
375
|
+
resource_id=f"account-{region}",
|
|
376
|
+
resource_type="AWS::::Account",
|
|
377
|
+
compliance_status=compliance_status,
|
|
378
|
+
evaluation_reason=evaluation_reason,
|
|
379
|
+
config_rule_name=self.rule_name,
|
|
380
|
+
region=region
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
def _get_rule_remediation_steps(self) -> List[str]:
|
|
384
|
+
"""Get remediation steps for enabling Inspector."""
|
|
385
|
+
return [
|
|
386
|
+
"1. Enable Inspector for EC2:",
|
|
387
|
+
" aws inspector2 enable \\",
|
|
388
|
+
" --resource-types EC2",
|
|
389
|
+
"",
|
|
390
|
+
"2. Enable Inspector for ECR:",
|
|
391
|
+
" aws inspector2 enable \\",
|
|
392
|
+
" --resource-types ECR",
|
|
393
|
+
"",
|
|
394
|
+
"3. Enable Inspector for Lambda:",
|
|
395
|
+
" aws inspector2 enable \\",
|
|
396
|
+
" --resource-types LAMBDA",
|
|
397
|
+
"",
|
|
398
|
+
"4. Enable all resource types:",
|
|
399
|
+
" aws inspector2 enable \\",
|
|
400
|
+
" --resource-types EC2 ECR LAMBDA",
|
|
401
|
+
"",
|
|
402
|
+
"Priority: HIGH - Critical for vulnerability management",
|
|
403
|
+
"Effort: Low - Quick enablement",
|
|
404
|
+
"",
|
|
405
|
+
"AWS Documentation:",
|
|
406
|
+
"https://docs.aws.amazon.com/inspector/latest/user/getting_started_tutorial.html"
|
|
407
|
+
]
|