regscale-cli 6.27.3.0__py3-none-any.whl → 6.28.0.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.
Potentially problematic release.
This version of regscale-cli might be problematic. Click here for more details.
- regscale/_version.py +1 -1
- regscale/core/app/utils/app_utils.py +11 -2
- regscale/dev/cli.py +26 -0
- regscale/dev/version.py +72 -0
- regscale/integrations/commercial/__init__.py +15 -1
- regscale/integrations/commercial/amazon/amazon/__init__.py +0 -0
- regscale/integrations/commercial/amazon/amazon/common.py +204 -0
- regscale/integrations/commercial/amazon/common.py +48 -58
- regscale/integrations/commercial/aws/audit_manager_compliance.py +2671 -0
- regscale/integrations/commercial/aws/cli.py +3093 -55
- regscale/integrations/commercial/aws/cloudtrail_control_mappings.py +333 -0
- regscale/integrations/commercial/aws/cloudtrail_evidence.py +501 -0
- regscale/integrations/commercial/aws/cloudwatch_control_mappings.py +357 -0
- regscale/integrations/commercial/aws/cloudwatch_evidence.py +490 -0
- regscale/integrations/commercial/aws/config_compliance.py +914 -0
- regscale/integrations/commercial/aws/conformance_pack_mappings.py +198 -0
- regscale/integrations/commercial/aws/evidence_generator.py +283 -0
- regscale/integrations/commercial/aws/guardduty_control_mappings.py +340 -0
- regscale/integrations/commercial/aws/guardduty_evidence.py +1053 -0
- regscale/integrations/commercial/aws/iam_control_mappings.py +368 -0
- regscale/integrations/commercial/aws/iam_evidence.py +574 -0
- regscale/integrations/commercial/aws/inventory/__init__.py +223 -22
- regscale/integrations/commercial/aws/inventory/base.py +107 -5
- regscale/integrations/commercial/aws/inventory/resources/audit_manager.py +513 -0
- regscale/integrations/commercial/aws/inventory/resources/cloudtrail.py +315 -0
- regscale/integrations/commercial/aws/inventory/resources/cloudtrail_logs_metadata.py +476 -0
- regscale/integrations/commercial/aws/inventory/resources/cloudwatch.py +191 -0
- regscale/integrations/commercial/aws/inventory/resources/compute.py +66 -9
- regscale/integrations/commercial/aws/inventory/resources/config.py +464 -0
- regscale/integrations/commercial/aws/inventory/resources/containers.py +74 -9
- regscale/integrations/commercial/aws/inventory/resources/database.py +106 -31
- regscale/integrations/commercial/aws/inventory/resources/guardduty.py +286 -0
- regscale/integrations/commercial/aws/inventory/resources/iam.py +470 -0
- regscale/integrations/commercial/aws/inventory/resources/inspector.py +476 -0
- regscale/integrations/commercial/aws/inventory/resources/integration.py +175 -61
- regscale/integrations/commercial/aws/inventory/resources/kms.py +447 -0
- regscale/integrations/commercial/aws/inventory/resources/networking.py +103 -67
- regscale/integrations/commercial/aws/inventory/resources/s3.py +394 -0
- regscale/integrations/commercial/aws/inventory/resources/security.py +268 -72
- regscale/integrations/commercial/aws/inventory/resources/securityhub.py +473 -0
- regscale/integrations/commercial/aws/inventory/resources/storage.py +53 -29
- regscale/integrations/commercial/aws/inventory/resources/systems_manager.py +657 -0
- regscale/integrations/commercial/aws/inventory/resources/vpc.py +655 -0
- regscale/integrations/commercial/aws/kms_control_mappings.py +288 -0
- regscale/integrations/commercial/aws/kms_evidence.py +879 -0
- regscale/integrations/commercial/aws/ocsf/__init__.py +7 -0
- regscale/integrations/commercial/aws/ocsf/constants.py +115 -0
- regscale/integrations/commercial/aws/ocsf/mapper.py +435 -0
- regscale/integrations/commercial/aws/org_control_mappings.py +286 -0
- regscale/integrations/commercial/aws/org_evidence.py +666 -0
- regscale/integrations/commercial/aws/s3_control_mappings.py +356 -0
- regscale/integrations/commercial/aws/s3_evidence.py +632 -0
- regscale/integrations/commercial/aws/scanner.py +851 -206
- regscale/integrations/commercial/aws/security_hub.py +319 -0
- regscale/integrations/commercial/aws/session_manager.py +282 -0
- regscale/integrations/commercial/aws/ssm_control_mappings.py +291 -0
- regscale/integrations/commercial/aws/ssm_evidence.py +492 -0
- regscale/integrations/compliance_integration.py +308 -38
- regscale/integrations/due_date_handler.py +3 -0
- regscale/integrations/scanner_integration.py +399 -84
- regscale/models/integration_models/cisa_kev_data.json +34 -4
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +17 -9
- regscale/models/regscale_models/assessment.py +2 -1
- regscale/models/regscale_models/control_objective.py +74 -5
- regscale/models/regscale_models/file.py +2 -0
- regscale/models/regscale_models/issue.py +2 -5
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/METADATA +1 -1
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/RECORD +112 -33
- tests/regscale/integrations/commercial/aws/__init__.py +0 -0
- tests/regscale/integrations/commercial/aws/test_audit_manager_compliance.py +1304 -0
- tests/regscale/integrations/commercial/aws/test_audit_manager_evidence_aggregation.py +341 -0
- tests/regscale/integrations/commercial/aws/test_aws_audit_manager_collector.py +1155 -0
- tests/regscale/integrations/commercial/aws/test_aws_cloudtrail_collector.py +534 -0
- tests/regscale/integrations/commercial/aws/test_aws_config_collector.py +400 -0
- tests/regscale/integrations/commercial/aws/test_aws_guardduty_collector.py +315 -0
- tests/regscale/integrations/commercial/aws/test_aws_iam_collector.py +458 -0
- tests/regscale/integrations/commercial/aws/test_aws_inspector_collector.py +353 -0
- tests/regscale/integrations/commercial/aws/test_aws_inventory_integration.py +530 -0
- tests/regscale/integrations/commercial/aws/test_aws_kms_collector.py +919 -0
- tests/regscale/integrations/commercial/aws/test_aws_s3_collector.py +722 -0
- tests/regscale/integrations/commercial/aws/test_aws_scanner_integration.py +722 -0
- tests/regscale/integrations/commercial/aws/test_aws_securityhub_collector.py +792 -0
- tests/regscale/integrations/commercial/aws/test_aws_systems_manager_collector.py +918 -0
- tests/regscale/integrations/commercial/aws/test_aws_vpc_collector.py +996 -0
- tests/regscale/integrations/commercial/aws/test_cli_evidence.py +431 -0
- tests/regscale/integrations/commercial/aws/test_cloudtrail_control_mappings.py +452 -0
- tests/regscale/integrations/commercial/aws/test_cloudtrail_evidence.py +788 -0
- tests/regscale/integrations/commercial/aws/test_config_compliance.py +298 -0
- tests/regscale/integrations/commercial/aws/test_conformance_pack_mappings.py +200 -0
- tests/regscale/integrations/commercial/aws/test_evidence_generator.py +386 -0
- tests/regscale/integrations/commercial/aws/test_guardduty_control_mappings.py +564 -0
- tests/regscale/integrations/commercial/aws/test_guardduty_evidence.py +1041 -0
- tests/regscale/integrations/commercial/aws/test_iam_control_mappings.py +718 -0
- tests/regscale/integrations/commercial/aws/test_iam_evidence.py +1375 -0
- tests/regscale/integrations/commercial/aws/test_kms_control_mappings.py +656 -0
- tests/regscale/integrations/commercial/aws/test_kms_evidence.py +1163 -0
- tests/regscale/integrations/commercial/aws/test_ocsf_mapper.py +370 -0
- tests/regscale/integrations/commercial/aws/test_org_control_mappings.py +546 -0
- tests/regscale/integrations/commercial/aws/test_org_evidence.py +1240 -0
- tests/regscale/integrations/commercial/aws/test_s3_control_mappings.py +672 -0
- tests/regscale/integrations/commercial/aws/test_s3_evidence.py +987 -0
- tests/regscale/integrations/commercial/aws/test_scanner_evidence.py +373 -0
- tests/regscale/integrations/commercial/aws/test_security_hub_config_filtering.py +539 -0
- tests/regscale/integrations/commercial/aws/test_session_manager.py +516 -0
- tests/regscale/integrations/commercial/aws/test_ssm_control_mappings.py +588 -0
- tests/regscale/integrations/commercial/aws/test_ssm_evidence.py +735 -0
- tests/regscale/integrations/commercial/test_aws.py +55 -56
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,722 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Integration tests for AWS scanner with simulated AWS data."""
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from unittest.mock import MagicMock, patch
|
|
7
|
+
from typing import Iterator
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
from regscale.integrations.commercial.aws.scanner import AWSInventoryIntegration
|
|
12
|
+
from regscale.integrations.scanner_integration import IntegrationAsset, IntegrationFinding
|
|
13
|
+
from regscale.models import regscale_models
|
|
14
|
+
|
|
15
|
+
PLAN_ID = 36
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@pytest.mark.integration
|
|
19
|
+
class TestAWSScannerIntegration:
|
|
20
|
+
"""Test suite for AWS scanner integration with mocked AWS responses.
|
|
21
|
+
|
|
22
|
+
These are integration tests that validate the AWS Security Hub integration
|
|
23
|
+
with simulated data. They test the full workflow of fetching findings from
|
|
24
|
+
AWS Security Hub and syncing them to RegScale.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
@pytest.fixture
|
|
28
|
+
def mock_regscale_app(self):
|
|
29
|
+
"""Create a mock RegScale application."""
|
|
30
|
+
app = MagicMock()
|
|
31
|
+
app.config = {
|
|
32
|
+
"issues": {
|
|
33
|
+
"amazon": {
|
|
34
|
+
"status": "Open",
|
|
35
|
+
"minimumSeverity": "LOW",
|
|
36
|
+
"low": 30,
|
|
37
|
+
"moderate": 15,
|
|
38
|
+
"high": 7,
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return app
|
|
43
|
+
|
|
44
|
+
@pytest.fixture
|
|
45
|
+
def scanner(self, mock_regscale_app):
|
|
46
|
+
"""Create an AWS scanner instance."""
|
|
47
|
+
scanner = AWSInventoryIntegration(plan_id=36)
|
|
48
|
+
scanner.app = mock_regscale_app
|
|
49
|
+
return scanner
|
|
50
|
+
|
|
51
|
+
@pytest.fixture
|
|
52
|
+
def mock_security_hub_findings(self):
|
|
53
|
+
"""Create realistic mock Security Hub findings data."""
|
|
54
|
+
return [
|
|
55
|
+
{
|
|
56
|
+
"Id": "arn:aws:securityhub:us-east-1:123456789012:subscription/aws-foundational-security-best-practices/v/1.0.0/S3.1/finding/a1b2c3d4-5678-90ab-cdef-EXAMPLE11111",
|
|
57
|
+
"ProductArn": "arn:aws:securityhub:us-east-1::product/aws/securityhub",
|
|
58
|
+
"ProductName": "Security Hub",
|
|
59
|
+
"CompanyName": "AWS",
|
|
60
|
+
"Region": "us-east-1",
|
|
61
|
+
"GeneratorId": "aws-foundational-security-best-practices/v/1.0.0/S3.1",
|
|
62
|
+
"AwsAccountId": "123456789012",
|
|
63
|
+
"Types": ["Software and Configuration Checks/AWS Security Best Practices"],
|
|
64
|
+
"FirstObservedAt": "2024-01-15T10:30:00.000Z",
|
|
65
|
+
"LastObservedAt": "2024-01-20T14:45:00.000Z",
|
|
66
|
+
"CreatedAt": "2024-01-15T10:30:00.000Z",
|
|
67
|
+
"UpdatedAt": "2024-01-20T14:45:00.000Z",
|
|
68
|
+
"Severity": {"Label": "HIGH", "Normalized": 70},
|
|
69
|
+
"Title": "S3.1 S3 Block Public Access setting should be enabled",
|
|
70
|
+
"Description": "This control checks whether S3 Block Public Access setting is enabled at the bucket level.",
|
|
71
|
+
"Remediation": {
|
|
72
|
+
"Recommendation": {
|
|
73
|
+
"Text": "Enable S3 Block Public Access at the bucket level",
|
|
74
|
+
"Url": "https://docs.aws.amazon.com/console/securityhub/S3.1/remediation",
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"ProductFields": {
|
|
78
|
+
"StandardsArn": "arn:aws:securityhub:::standards/aws-foundational-security-best-practices/v/1.0.0",
|
|
79
|
+
"StandardsSubscriptionArn": "arn:aws:securityhub:us-east-1:123456789012:subscription/aws-foundational-security-best-practices/v/1.0.0",
|
|
80
|
+
"ControlId": "S3.1",
|
|
81
|
+
"RecommendationUrl": "https://docs.aws.amazon.com/console/securityhub/S3.1/remediation",
|
|
82
|
+
"RelatedAWSResources:0/name": "securityhub-s3-bucket-public-write-prohibited",
|
|
83
|
+
"RelatedAWSResources:0/type": "AWS::Config::ConfigRule",
|
|
84
|
+
"StandardsControlArn": "arn:aws:securityhub:us-east-1:123456789012:control/aws-foundational-security-best-practices/v/1.0.0/S3.1",
|
|
85
|
+
"aws/securityhub/ProductName": "Security Hub",
|
|
86
|
+
"aws/securityhub/CompanyName": "AWS",
|
|
87
|
+
"Resources:0/Id": "arn:aws:s3:::my-test-bucket",
|
|
88
|
+
"aws/securityhub/FindingId": "arn:aws:securityhub:us-east-1::product/aws/securityhub/arn:aws:securityhub:us-east-1:123456789012:subscription/aws-foundational-security-best-practices/v/1.0.0/S3.1/finding/a1b2c3d4-5678-90ab-cdef-EXAMPLE11111",
|
|
89
|
+
},
|
|
90
|
+
"Resources": [
|
|
91
|
+
{
|
|
92
|
+
"Type": "AwsS3Bucket",
|
|
93
|
+
"Id": "arn:aws:s3:::my-test-bucket",
|
|
94
|
+
"Partition": "aws",
|
|
95
|
+
"Region": "us-east-1",
|
|
96
|
+
"Details": {
|
|
97
|
+
"AwsS3Bucket": {
|
|
98
|
+
"OwnerId": "123456789012",
|
|
99
|
+
"OwnerName": "test-owner",
|
|
100
|
+
"CreatedAt": "2024-01-01T00:00:00.000Z",
|
|
101
|
+
"Name": "my-test-bucket",
|
|
102
|
+
"PublicAccessBlockConfiguration": {
|
|
103
|
+
"BlockPublicAcls": False,
|
|
104
|
+
"BlockPublicPolicy": False,
|
|
105
|
+
"IgnorePublicAcls": False,
|
|
106
|
+
"RestrictPublicBuckets": False,
|
|
107
|
+
},
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
}
|
|
111
|
+
],
|
|
112
|
+
"Compliance": {"Status": "FAILED"},
|
|
113
|
+
"WorkflowState": "NEW",
|
|
114
|
+
"Workflow": {"Status": "NEW"},
|
|
115
|
+
"RecordState": "ACTIVE",
|
|
116
|
+
"FindingProviderFields": {
|
|
117
|
+
"Severity": {"Label": "HIGH"},
|
|
118
|
+
"Types": ["Software and Configuration Checks"],
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"Id": "arn:aws:securityhub:us-east-1:123456789012:subscription/aws-foundational-security-best-practices/v/1.0.0/EC2.1/finding/b2c3d4e5-6789-01ab-cdef-EXAMPLE22222",
|
|
123
|
+
"ProductArn": "arn:aws:securityhub:us-east-1::product/aws/securityhub",
|
|
124
|
+
"ProductName": "Security Hub",
|
|
125
|
+
"CompanyName": "AWS",
|
|
126
|
+
"Region": "us-east-1",
|
|
127
|
+
"GeneratorId": "aws-foundational-security-best-practices/v/1.0.0/EC2.1",
|
|
128
|
+
"AwsAccountId": "123456789012",
|
|
129
|
+
"Types": ["Software and Configuration Checks/AWS Security Best Practices"],
|
|
130
|
+
"FirstObservedAt": "2024-01-16T11:00:00.000Z",
|
|
131
|
+
"LastObservedAt": "2024-01-20T15:00:00.000Z",
|
|
132
|
+
"CreatedAt": "2024-01-16T11:00:00.000Z",
|
|
133
|
+
"UpdatedAt": "2024-01-20T15:00:00.000Z",
|
|
134
|
+
"Severity": {"Label": "MEDIUM", "Normalized": 40},
|
|
135
|
+
"Title": "EC2.1 Amazon EBS snapshots should not be publicly restorable",
|
|
136
|
+
"Description": "This control checks whether Amazon EBS snapshots are restorable by everyone.",
|
|
137
|
+
"Remediation": {
|
|
138
|
+
"Recommendation": {
|
|
139
|
+
"Text": "Modify EBS snapshot permissions to remove public access",
|
|
140
|
+
"Url": "https://docs.aws.amazon.com/console/securityhub/EC2.1/remediation",
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
"ProductFields": {
|
|
144
|
+
"StandardsArn": "arn:aws:securityhub:::standards/aws-foundational-security-best-practices/v/1.0.0",
|
|
145
|
+
"StandardsSubscriptionArn": "arn:aws:securityhub:us-east-1:123456789012:subscription/aws-foundational-security-best-practices/v/1.0.0",
|
|
146
|
+
"ControlId": "EC2.1",
|
|
147
|
+
"RecommendationUrl": "https://docs.aws.amazon.com/console/securityhub/EC2.1/remediation",
|
|
148
|
+
"RelatedAWSResources:0/name": "securityhub-ec2-ebs-snapshot-public-restorable",
|
|
149
|
+
"RelatedAWSResources:0/type": "AWS::Config::ConfigRule",
|
|
150
|
+
"StandardsControlArn": "arn:aws:securityhub:us-east-1:123456789012:control/aws-foundational-security-best-practices/v/1.0.0/EC2.1",
|
|
151
|
+
"aws/securityhub/ProductName": "Security Hub",
|
|
152
|
+
"aws/securityhub/CompanyName": "AWS",
|
|
153
|
+
"Resources:0/Id": "arn:aws:ec2:us-east-1:123456789012:instance/i-1234567890abcdef0",
|
|
154
|
+
"aws/securityhub/FindingId": "arn:aws:securityhub:us-east-1::product/aws/securityhub/arn:aws:securityhub:us-east-1:123456789012:subscription/aws-foundational-security-best-practices/v/1.0.0/EC2.1/finding/b2c3d4e5-6789-01ab-cdef-EXAMPLE22222",
|
|
155
|
+
},
|
|
156
|
+
"Resources": [
|
|
157
|
+
{
|
|
158
|
+
"Type": "AwsEc2Instance",
|
|
159
|
+
"Id": "arn:aws:ec2:us-east-1:123456789012:instance/i-1234567890abcdef0",
|
|
160
|
+
"Partition": "aws",
|
|
161
|
+
"Region": "us-east-1",
|
|
162
|
+
"Tags": {"Name": "WebServer-01"},
|
|
163
|
+
"Details": {
|
|
164
|
+
"AwsEc2Instance": {
|
|
165
|
+
"Type": "t3.medium",
|
|
166
|
+
"ImageId": "ami-0abcdef1234567890",
|
|
167
|
+
"IpV4Addresses": ["10.0.1.100", "54.123.45.67"],
|
|
168
|
+
"IpV6Addresses": [],
|
|
169
|
+
"KeyName": "my-keypair",
|
|
170
|
+
"IamInstanceProfileArn": "arn:aws:iam::123456789012:instance-profile/EC2-Role",
|
|
171
|
+
"VpcId": "vpc-12345678",
|
|
172
|
+
"SubnetId": "subnet-12345678",
|
|
173
|
+
"LaunchedAt": "2024-01-10T08:00:00.000Z",
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
}
|
|
177
|
+
],
|
|
178
|
+
"Compliance": {"Status": "FAILED"},
|
|
179
|
+
"WorkflowState": "NEW",
|
|
180
|
+
"Workflow": {"Status": "NEW"},
|
|
181
|
+
"RecordState": "ACTIVE",
|
|
182
|
+
"FindingProviderFields": {
|
|
183
|
+
"Severity": {"Label": "MEDIUM"},
|
|
184
|
+
"Types": ["Software and Configuration Checks"],
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
"Id": "arn:aws:securityhub:us-east-1:123456789012:subscription/aws-foundational-security-best-practices/v/1.0.0/IAM.1/finding/c3d4e5f6-7890-12ab-cdef-EXAMPLE33333",
|
|
189
|
+
"ProductArn": "arn:aws:securityhub:us-east-1::product/aws/securityhub",
|
|
190
|
+
"ProductName": "Security Hub",
|
|
191
|
+
"CompanyName": "AWS",
|
|
192
|
+
"Region": "us-east-1",
|
|
193
|
+
"GeneratorId": "aws-foundational-security-best-practices/v/1.0.0/IAM.1",
|
|
194
|
+
"AwsAccountId": "123456789012",
|
|
195
|
+
"Types": ["Software and Configuration Checks/AWS Security Best Practices/IAM"],
|
|
196
|
+
"FirstObservedAt": "2024-01-14T09:00:00.000Z",
|
|
197
|
+
"LastObservedAt": "2024-01-20T16:00:00.000Z",
|
|
198
|
+
"CreatedAt": "2024-01-14T09:00:00.000Z",
|
|
199
|
+
"UpdatedAt": "2024-01-20T16:00:00.000Z",
|
|
200
|
+
"Severity": {"Label": "CRITICAL", "Normalized": 90},
|
|
201
|
+
"Title": "IAM.1 IAM policies should not allow full '*' administrative privileges",
|
|
202
|
+
"Description": "This control checks whether IAM policies that you create grant full '*:*' administrative privileges.",
|
|
203
|
+
"Remediation": {
|
|
204
|
+
"Recommendation": {
|
|
205
|
+
"Text": "Follow the principle of least privilege and grant only necessary permissions",
|
|
206
|
+
"Url": "https://docs.aws.amazon.com/console/securityhub/IAM.1/remediation",
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
"ProductFields": {
|
|
210
|
+
"StandardsArn": "arn:aws:securityhub:::standards/aws-foundational-security-best-practices/v/1.0.0",
|
|
211
|
+
"StandardsSubscriptionArn": "arn:aws:securityhub:us-east-1:123456789012:subscription/aws-foundational-security-best-practices/v/1.0.0",
|
|
212
|
+
"ControlId": "IAM.1",
|
|
213
|
+
"RecommendationUrl": "https://docs.aws.amazon.com/console/securityhub/IAM.1/remediation",
|
|
214
|
+
"RelatedAWSResources:0/name": "securityhub-iam-policy-no-statements-with-admin-access",
|
|
215
|
+
"RelatedAWSResources:0/type": "AWS::Config::ConfigRule",
|
|
216
|
+
"StandardsControlArn": "arn:aws:securityhub:us-east-1:123456789012:control/aws-foundational-security-best-practices/v/1.0.0/IAM.1",
|
|
217
|
+
"aws/securityhub/ProductName": "Security Hub",
|
|
218
|
+
"aws/securityhub/CompanyName": "AWS",
|
|
219
|
+
"Resources:0/Id": "arn:aws:iam::123456789012:user/admin-user",
|
|
220
|
+
"aws/securityhub/FindingId": "arn:aws:securityhub:us-east-1::product/aws/securityhub/arn:aws:securityhub:us-east-1:123456789012:subscription/aws-foundational-security-best-practices/v/1.0.0/IAM.1/finding/c3d4e5f6-7890-12ab-cdef-EXAMPLE33333",
|
|
221
|
+
},
|
|
222
|
+
"Resources": [
|
|
223
|
+
{
|
|
224
|
+
"Type": "AwsIamUser",
|
|
225
|
+
"Id": "arn:aws:iam::123456789012:user/admin-user",
|
|
226
|
+
"Partition": "aws",
|
|
227
|
+
"Region": "us-east-1",
|
|
228
|
+
"Details": {
|
|
229
|
+
"AwsIamUser": {
|
|
230
|
+
"AttachedManagedPolicies": [
|
|
231
|
+
{
|
|
232
|
+
"PolicyName": "AdministratorAccess",
|
|
233
|
+
"PolicyArn": "arn:aws:iam::aws:policy/AdministratorAccess",
|
|
234
|
+
}
|
|
235
|
+
],
|
|
236
|
+
"CreateDate": "2023-12-01T10:00:00.000Z",
|
|
237
|
+
"UserName": "admin-user",
|
|
238
|
+
"UserId": "AIDACKCEVSQ6C2EXAMPLE",
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
}
|
|
242
|
+
],
|
|
243
|
+
"Compliance": {"Status": "FAILED"},
|
|
244
|
+
"WorkflowState": "NEW",
|
|
245
|
+
"Workflow": {"Status": "NEW"},
|
|
246
|
+
"RecordState": "ACTIVE",
|
|
247
|
+
"FindingProviderFields": {
|
|
248
|
+
"Severity": {"Label": "CRITICAL"},
|
|
249
|
+
"Types": ["Software and Configuration Checks/AWS Security Best Practices/IAM"],
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
]
|
|
253
|
+
|
|
254
|
+
@pytest.fixture
|
|
255
|
+
def mock_boto_session(self, mock_security_hub_findings):
|
|
256
|
+
"""Create a mock boto3 session with Security Hub client."""
|
|
257
|
+
session = MagicMock()
|
|
258
|
+
client = MagicMock()
|
|
259
|
+
|
|
260
|
+
# Mock the get_findings method to return our test findings
|
|
261
|
+
client.get_findings.return_value = {"Findings": mock_security_hub_findings}
|
|
262
|
+
|
|
263
|
+
# Mock paginator for get_findings (in case pagination is used)
|
|
264
|
+
paginator = MagicMock()
|
|
265
|
+
paginator.paginate.return_value = [{"Findings": mock_security_hub_findings}]
|
|
266
|
+
client.get_paginator.return_value = paginator
|
|
267
|
+
|
|
268
|
+
# Mock session metadata for region
|
|
269
|
+
session.meta.region_name = "us-east-1"
|
|
270
|
+
session.client.return_value = client
|
|
271
|
+
return session
|
|
272
|
+
|
|
273
|
+
def test_sync_findings_only(self, scanner, mock_boto_session, mock_security_hub_findings):
|
|
274
|
+
"""Test that fetch_findings correctly retrieves and parses AWS Security Hub findings."""
|
|
275
|
+
with patch("boto3.Session", return_value=mock_boto_session):
|
|
276
|
+
# Mock the fetch_aws_findings function to return our test data
|
|
277
|
+
with patch("regscale.integrations.commercial.amazon.common.fetch_aws_findings") as mock_fetch:
|
|
278
|
+
mock_fetch.return_value = mock_security_hub_findings
|
|
279
|
+
|
|
280
|
+
# Call fetch_findings to get parsed findings
|
|
281
|
+
findings_iterator = scanner.fetch_findings(
|
|
282
|
+
region="us-east-1",
|
|
283
|
+
profile="test-profile",
|
|
284
|
+
aws_access_key_id=None,
|
|
285
|
+
aws_secret_access_key=None,
|
|
286
|
+
aws_session_token=None,
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
# Convert iterator to list
|
|
290
|
+
findings_list = list(findings_iterator)
|
|
291
|
+
|
|
292
|
+
# Should have findings for 3 different resources
|
|
293
|
+
assert len(findings_list) == 3, f"Expected 3 findings but got {len(findings_list)}"
|
|
294
|
+
|
|
295
|
+
# Verify findings were properly parsed
|
|
296
|
+
for finding in findings_list:
|
|
297
|
+
assert isinstance(finding, IntegrationFinding)
|
|
298
|
+
assert finding.title is not None
|
|
299
|
+
assert finding.severity is not None
|
|
300
|
+
|
|
301
|
+
# Verify assets were discovered during finding fetch
|
|
302
|
+
assert len(scanner.discovered_assets) == 3, "Should have discovered 3 assets from findings"
|
|
303
|
+
|
|
304
|
+
def test_sync_findings_and_assets(self, scanner, mock_boto_session, mock_security_hub_findings):
|
|
305
|
+
"""Test syncing findings and automatically discovered assets."""
|
|
306
|
+
with patch("boto3.Session", return_value=mock_boto_session):
|
|
307
|
+
# Mock the fetch_aws_findings function to return our test data
|
|
308
|
+
with patch("regscale.integrations.commercial.amazon.common.fetch_aws_findings") as mock_fetch:
|
|
309
|
+
mock_fetch.return_value = mock_security_hub_findings
|
|
310
|
+
|
|
311
|
+
with patch.object(scanner, "update_regscale_assets") as mock_update_assets:
|
|
312
|
+
with patch.object(scanner, "update_regscale_findings") as mock_update_findings:
|
|
313
|
+
mock_update_assets.return_value = 3 # 3 assets discovered
|
|
314
|
+
mock_update_findings.return_value = 3 # 3 findings processed
|
|
315
|
+
|
|
316
|
+
# Call sync_findings_and_assets
|
|
317
|
+
findings_count, assets_count = scanner.sync_findings_and_assets(
|
|
318
|
+
plan_id=PLAN_ID,
|
|
319
|
+
region="us-east-1",
|
|
320
|
+
profile="test-profile",
|
|
321
|
+
aws_access_key_id=None,
|
|
322
|
+
aws_secret_access_key=None,
|
|
323
|
+
aws_session_token=None,
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
# Verify counts
|
|
327
|
+
assert findings_count == 3
|
|
328
|
+
assert assets_count == 3
|
|
329
|
+
|
|
330
|
+
# Verify assets were created first, then findings
|
|
331
|
+
assert mock_update_assets.call_count == 1
|
|
332
|
+
assert mock_update_findings.call_count == 1
|
|
333
|
+
|
|
334
|
+
def test_parse_finding_creates_integration_finding(self, scanner):
|
|
335
|
+
"""Test that parse_finding correctly creates IntegrationFinding objects."""
|
|
336
|
+
test_finding = {
|
|
337
|
+
"Id": "test-finding-id",
|
|
338
|
+
"Title": "Test Security Finding",
|
|
339
|
+
"Description": "This is a test finding",
|
|
340
|
+
"Severity": {"Label": "HIGH"},
|
|
341
|
+
"CreatedAt": "2024-01-15T10:30:00.000Z",
|
|
342
|
+
"Remediation": {
|
|
343
|
+
"Recommendation": {
|
|
344
|
+
"Text": "Fix this issue",
|
|
345
|
+
"Url": "https://example.com/fix",
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
"Types": ["Software and Configuration Checks"],
|
|
349
|
+
"Resources": [
|
|
350
|
+
{
|
|
351
|
+
"Type": "AwsS3Bucket",
|
|
352
|
+
"Id": "arn:aws:s3:::test-bucket",
|
|
353
|
+
"Region": "us-east-1",
|
|
354
|
+
"Details": {"AwsS3Bucket": {"Name": "test-bucket"}},
|
|
355
|
+
}
|
|
356
|
+
],
|
|
357
|
+
"Compliance": {"Status": "FAILED"},
|
|
358
|
+
"FindingProviderFields": {
|
|
359
|
+
"Severity": {"Label": "HIGH"},
|
|
360
|
+
"Types": ["Software and Configuration Checks"],
|
|
361
|
+
},
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
findings = scanner.parse_finding(test_finding)
|
|
365
|
+
|
|
366
|
+
# If no findings returned, there was likely an exception or filtering issue
|
|
367
|
+
assert len(findings) == 1, f"Expected 1 finding but got {len(findings)}"
|
|
368
|
+
finding = findings[0]
|
|
369
|
+
|
|
370
|
+
# Verify IntegrationFinding fields
|
|
371
|
+
assert isinstance(finding, IntegrationFinding)
|
|
372
|
+
assert finding.title == "Test Security Finding"
|
|
373
|
+
assert finding.description == "This is a test finding"
|
|
374
|
+
assert finding.severity == regscale_models.IssueSeverity.High
|
|
375
|
+
assert finding.recommendation_for_mitigation == "Fix this issue"
|
|
376
|
+
# Note: asset_identifier uses extract_name_from_arn which for S3 ARNs returns the full ARN
|
|
377
|
+
assert finding.asset_identifier == "arn:aws:s3:::test-bucket"
|
|
378
|
+
|
|
379
|
+
def test_parse_resource_to_asset_s3_bucket(self, scanner):
|
|
380
|
+
"""Test parsing S3 bucket resource to IntegrationAsset."""
|
|
381
|
+
resource = {
|
|
382
|
+
"Type": "AwsS3Bucket",
|
|
383
|
+
"Id": "arn:aws:s3:::my-test-bucket",
|
|
384
|
+
"Region": "us-east-1",
|
|
385
|
+
"Details": {
|
|
386
|
+
"AwsS3Bucket": {
|
|
387
|
+
"Name": "my-test-bucket",
|
|
388
|
+
"OwnerId": "123456789012",
|
|
389
|
+
"CreatedAt": "2024-01-01T00:00:00.000Z",
|
|
390
|
+
}
|
|
391
|
+
},
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
finding = {"Id": "test-finding"}
|
|
395
|
+
|
|
396
|
+
asset = scanner.parse_resource_to_asset(resource, finding)
|
|
397
|
+
|
|
398
|
+
assert isinstance(asset, IntegrationAsset)
|
|
399
|
+
# The scanner's extract_name_from_arn returns the full ARN for S3 buckets without "/"
|
|
400
|
+
# Then uses "or" operator to fallback to Details, but since ARN is truthy it uses the ARN
|
|
401
|
+
# This is the current behavior - the test validates what actually happens
|
|
402
|
+
assert asset.name == "S3 Bucket: arn:aws:s3:::my-test-bucket"
|
|
403
|
+
assert asset.identifier == "arn:aws:s3:::my-test-bucket"
|
|
404
|
+
assert asset.asset_type == regscale_models.AssetType.Other
|
|
405
|
+
assert asset.manufacturer == "AWS"
|
|
406
|
+
assert asset.aws_identifier == "arn:aws:s3:::my-test-bucket"
|
|
407
|
+
assert asset.is_virtual is True
|
|
408
|
+
|
|
409
|
+
def test_parse_resource_to_asset_ec2_instance(self, scanner):
|
|
410
|
+
"""Test parsing EC2 instance resource to IntegrationAsset."""
|
|
411
|
+
resource = {
|
|
412
|
+
"Type": "AwsEc2Instance",
|
|
413
|
+
"Id": "arn:aws:ec2:us-east-1:123456789012:instance/i-1234567890abcdef0",
|
|
414
|
+
"Region": "us-east-1",
|
|
415
|
+
"Tags": {"Name": "WebServer-01"},
|
|
416
|
+
"Details": {
|
|
417
|
+
"AwsEc2Instance": {
|
|
418
|
+
"Type": "t3.medium",
|
|
419
|
+
"ImageId": "ami-0abcdef1234567890",
|
|
420
|
+
"VpcId": "vpc-12345678",
|
|
421
|
+
"SubnetId": "subnet-12345678",
|
|
422
|
+
}
|
|
423
|
+
},
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
finding = {"Id": "test-finding"}
|
|
427
|
+
|
|
428
|
+
asset = scanner.parse_resource_to_asset(resource, finding)
|
|
429
|
+
|
|
430
|
+
assert isinstance(asset, IntegrationAsset)
|
|
431
|
+
assert asset.name == "EC2: WebServer-01 (t3.medium)"
|
|
432
|
+
assert asset.identifier == "i-1234567890abcdef0"
|
|
433
|
+
assert asset.asset_type == regscale_models.AssetType.VM
|
|
434
|
+
assert asset.manufacturer == "AWS"
|
|
435
|
+
assert asset.model == "t3.medium"
|
|
436
|
+
assert asset.is_virtual is True
|
|
437
|
+
|
|
438
|
+
def test_parse_resource_to_asset_iam_user(self, scanner):
|
|
439
|
+
"""Test parsing IAM user resource to IntegrationAsset."""
|
|
440
|
+
resource = {
|
|
441
|
+
"Type": "AwsIamUser",
|
|
442
|
+
"Id": "arn:aws:iam::123456789012:user/admin-user",
|
|
443
|
+
"Region": "us-east-1",
|
|
444
|
+
"Details": {"AwsIamUser": {"UserName": "admin-user", "UserId": "AIDACKCEVSQ6C2EXAMPLE"}},
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
finding = {"Id": "test-finding"}
|
|
448
|
+
|
|
449
|
+
asset = scanner.parse_resource_to_asset(resource, finding)
|
|
450
|
+
|
|
451
|
+
assert isinstance(asset, IntegrationAsset)
|
|
452
|
+
assert asset.name == "IAM User: admin-user"
|
|
453
|
+
assert asset.identifier == "admin-user"
|
|
454
|
+
assert asset.asset_type == regscale_models.AssetType.Other
|
|
455
|
+
assert asset.manufacturer == "AWS"
|
|
456
|
+
assert asset.is_virtual is True
|
|
457
|
+
|
|
458
|
+
def test_should_process_finding_by_severity(self, scanner):
|
|
459
|
+
"""Test severity filtering logic."""
|
|
460
|
+
# Test HIGH severity (should process)
|
|
461
|
+
assert scanner.should_process_finding_by_severity("HIGH") is True
|
|
462
|
+
|
|
463
|
+
# Test CRITICAL severity (should process)
|
|
464
|
+
assert scanner.should_process_finding_by_severity("CRITICAL") is True
|
|
465
|
+
|
|
466
|
+
# Test MEDIUM severity (should process)
|
|
467
|
+
assert scanner.should_process_finding_by_severity("MEDIUM") is True
|
|
468
|
+
|
|
469
|
+
# Test LOW severity (should process - it's the minimum)
|
|
470
|
+
assert scanner.should_process_finding_by_severity("LOW") is True
|
|
471
|
+
|
|
472
|
+
# Now test with higher minimum severity
|
|
473
|
+
scanner.app.config["issues"]["amazon"]["minimumSeverity"] = "HIGH"
|
|
474
|
+
|
|
475
|
+
# Test HIGH severity (should process)
|
|
476
|
+
assert scanner.should_process_finding_by_severity("HIGH") is True
|
|
477
|
+
|
|
478
|
+
# Test MEDIUM severity (should NOT process)
|
|
479
|
+
assert scanner.should_process_finding_by_severity("MEDIUM") is False
|
|
480
|
+
|
|
481
|
+
# Test LOW severity (should NOT process)
|
|
482
|
+
assert scanner.should_process_finding_by_severity("LOW") is False
|
|
483
|
+
|
|
484
|
+
def test_extract_name_from_arn(self, scanner):
|
|
485
|
+
"""Test extracting resource names from ARNs."""
|
|
486
|
+
# Test with slash separator
|
|
487
|
+
arn = "arn:aws:s3:::my-bucket/object-key"
|
|
488
|
+
assert scanner.extract_name_from_arn(arn) == "object-key"
|
|
489
|
+
|
|
490
|
+
# Test with colon separator
|
|
491
|
+
arn = "arn:aws:ec2:us-east-1:123456789012:instance/i-1234567890abcdef0"
|
|
492
|
+
assert scanner.extract_name_from_arn(arn) == "i-1234567890abcdef0"
|
|
493
|
+
|
|
494
|
+
# Test simple name
|
|
495
|
+
arn = "my-simple-name"
|
|
496
|
+
assert scanner.extract_name_from_arn(arn) == "my-simple-name"
|
|
497
|
+
|
|
498
|
+
def test_get_baseline(self, scanner):
|
|
499
|
+
"""Test baseline determination from resource type."""
|
|
500
|
+
assert scanner.get_baseline({"Type": "AwsAccount"}) == "AWS Account"
|
|
501
|
+
assert scanner.get_baseline({"Type": "AwsS3Bucket"}) == "S3 Bucket"
|
|
502
|
+
assert scanner.get_baseline({"Type": "AwsIamRole"}) == "IAM Role"
|
|
503
|
+
assert scanner.get_baseline({"Type": "AwsEc2Instance"}) == "EC2 Instance"
|
|
504
|
+
assert scanner.get_baseline({"Type": "UnknownType"}) == "UnknownType"
|
|
505
|
+
|
|
506
|
+
def test_fetch_findings_with_no_findings(self, scanner):
|
|
507
|
+
"""Test fetch_findings when no findings are returned from AWS."""
|
|
508
|
+
mock_session = MagicMock()
|
|
509
|
+
mock_client = MagicMock()
|
|
510
|
+
mock_client.get_findings.return_value = {"Findings": []}
|
|
511
|
+
mock_session.client.return_value = mock_client
|
|
512
|
+
mock_session.meta.region_name = "us-east-1"
|
|
513
|
+
|
|
514
|
+
with patch("boto3.Session", return_value=mock_session):
|
|
515
|
+
# Mock fetch_aws_findings to return empty list
|
|
516
|
+
with patch("regscale.integrations.commercial.amazon.common.fetch_aws_findings") as mock_fetch:
|
|
517
|
+
mock_fetch.return_value = []
|
|
518
|
+
|
|
519
|
+
findings = list(
|
|
520
|
+
scanner.fetch_findings(
|
|
521
|
+
region="us-east-1",
|
|
522
|
+
profile="test",
|
|
523
|
+
aws_access_key_id=None,
|
|
524
|
+
aws_secret_access_key=None,
|
|
525
|
+
aws_session_token=None,
|
|
526
|
+
)
|
|
527
|
+
)
|
|
528
|
+
|
|
529
|
+
assert len(findings) == 0
|
|
530
|
+
|
|
531
|
+
def test_discovered_assets_tracking(self, scanner, mock_boto_session, mock_security_hub_findings):
|
|
532
|
+
"""Test that discovered assets are properly tracked and deduplicated."""
|
|
533
|
+
with patch("boto3.Session", return_value=mock_boto_session):
|
|
534
|
+
# Mock fetch_aws_findings to return our test data
|
|
535
|
+
with patch("regscale.integrations.commercial.amazon.common.fetch_aws_findings") as mock_fetch:
|
|
536
|
+
mock_fetch.return_value = mock_security_hub_findings
|
|
537
|
+
|
|
538
|
+
# Fetch findings (this will discover assets)
|
|
539
|
+
findings = list(
|
|
540
|
+
scanner.fetch_findings(
|
|
541
|
+
region="us-east-1",
|
|
542
|
+
profile="test",
|
|
543
|
+
aws_access_key_id=None,
|
|
544
|
+
aws_secret_access_key=None,
|
|
545
|
+
aws_session_token=None,
|
|
546
|
+
)
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
# Verify findings were parsed
|
|
550
|
+
assert len(findings) == 3
|
|
551
|
+
|
|
552
|
+
# Verify assets were discovered
|
|
553
|
+
assert len(scanner.discovered_assets) == 3
|
|
554
|
+
|
|
555
|
+
# Verify asset identifiers are tracked
|
|
556
|
+
assert len(scanner.processed_asset_identifiers) == 3
|
|
557
|
+
|
|
558
|
+
def test_sync_findings_with_authentication_error(self, scanner):
|
|
559
|
+
"""Test sync_findings handles authentication errors gracefully."""
|
|
560
|
+
with patch("boto3.Session") as mock_session_class:
|
|
561
|
+
mock_session = MagicMock()
|
|
562
|
+
mock_client = MagicMock()
|
|
563
|
+
mock_session.client.return_value = mock_client
|
|
564
|
+
mock_session.meta.region_name = "us-east-1"
|
|
565
|
+
mock_session_class.return_value = mock_session
|
|
566
|
+
|
|
567
|
+
# Mock fetch_aws_findings to raise authentication error
|
|
568
|
+
with patch("regscale.integrations.commercial.amazon.common.fetch_aws_findings") as mock_fetch:
|
|
569
|
+
mock_fetch.side_effect = Exception("The security token included in the request is invalid")
|
|
570
|
+
|
|
571
|
+
with pytest.raises(Exception) as exc_info:
|
|
572
|
+
list(
|
|
573
|
+
scanner.fetch_findings(
|
|
574
|
+
region="us-east-1",
|
|
575
|
+
profile="test",
|
|
576
|
+
aws_access_key_id=None,
|
|
577
|
+
aws_secret_access_key=None,
|
|
578
|
+
aws_session_token=None,
|
|
579
|
+
)
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
assert "security token" in str(exc_info.value).lower()
|
|
583
|
+
|
|
584
|
+
def test_asset_identifier_matches_finding_asset_identifier(self, scanner):
|
|
585
|
+
"""Test that asset aws_identifier uses full ARN matching finding asset_identifier.
|
|
586
|
+
|
|
587
|
+
This test verifies the fix for vulnerability to asset mapping. The asset's
|
|
588
|
+
aws_identifier field must contain the full ARN (not just the short resource ID)
|
|
589
|
+
so that it matches the finding's asset_identifier field.
|
|
590
|
+
"""
|
|
591
|
+
# Create a mock Security Hub finding with full ARN in resource ID
|
|
592
|
+
finding_data = {
|
|
593
|
+
"Id": "arn:aws:securityhub:us-east-1:123456789012:subscription/test/finding/12345",
|
|
594
|
+
"ProductArn": "arn:aws:securityhub:us-east-1::product/aws/securityhub",
|
|
595
|
+
"GeneratorId": "aws-foundational-security-best-practices/v/1.0.0/S3.1",
|
|
596
|
+
"AwsAccountId": "123456789012",
|
|
597
|
+
"Types": ["Software and Configuration Checks"],
|
|
598
|
+
"CreatedAt": "2024-01-15T10:30:00.000Z",
|
|
599
|
+
"UpdatedAt": "2024-01-15T10:30:00.000Z",
|
|
600
|
+
"Severity": {"Label": "HIGH", "Normalized": 70},
|
|
601
|
+
"Title": "Test Finding",
|
|
602
|
+
"Description": "Test Description",
|
|
603
|
+
"Remediation": {"Recommendation": {"Text": "Fix the issue", "Url": "https://example.com"}},
|
|
604
|
+
"Resources": [
|
|
605
|
+
{
|
|
606
|
+
"Type": "AwsS3Bucket",
|
|
607
|
+
"Id": "arn:aws:s3:::test-bucket", # Full ARN
|
|
608
|
+
"Region": "us-east-1",
|
|
609
|
+
"Details": {"AwsS3Bucket": {"Name": "test-bucket"}},
|
|
610
|
+
}
|
|
611
|
+
],
|
|
612
|
+
"Compliance": {"Status": "FAILED"},
|
|
613
|
+
"WorkflowState": "NEW",
|
|
614
|
+
"Workflow": {"Status": "NEW"},
|
|
615
|
+
"RecordState": "ACTIVE",
|
|
616
|
+
"FindingProviderFields": {"Severity": {"Label": "HIGH"}, "Types": ["Software and Configuration Checks"]},
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
# Parse the finding to get the resource
|
|
620
|
+
resource = finding_data["Resources"][0]
|
|
621
|
+
|
|
622
|
+
# Parse resource to asset
|
|
623
|
+
asset = scanner.parse_resource_to_asset(resource, finding_data)
|
|
624
|
+
|
|
625
|
+
# Parse finding to get integration finding
|
|
626
|
+
findings = scanner.parse_finding(finding_data)
|
|
627
|
+
|
|
628
|
+
assert asset is not None, "Asset should be created"
|
|
629
|
+
assert len(findings) > 0, "Finding should be created"
|
|
630
|
+
|
|
631
|
+
integration_finding = findings[0]
|
|
632
|
+
|
|
633
|
+
# The key assertion: asset's aws_identifier should be the full ARN
|
|
634
|
+
assert (
|
|
635
|
+
asset.aws_identifier == "arn:aws:s3:::test-bucket"
|
|
636
|
+
), f"Asset aws_identifier should be full ARN, got: {asset.aws_identifier}"
|
|
637
|
+
|
|
638
|
+
# The finding's asset_identifier should also be the full ARN
|
|
639
|
+
assert (
|
|
640
|
+
integration_finding.asset_identifier == "arn:aws:s3:::test-bucket"
|
|
641
|
+
), f"Finding asset_identifier should be full ARN, got: {integration_finding.asset_identifier}"
|
|
642
|
+
|
|
643
|
+
# Critical: These must match for vulnerability mapping to work
|
|
644
|
+
assert asset.aws_identifier == integration_finding.asset_identifier, (
|
|
645
|
+
"Asset aws_identifier must match finding asset_identifier for vulnerability mapping. "
|
|
646
|
+
f"Asset: {asset.aws_identifier}, Finding: {integration_finding.asset_identifier}"
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
def test_all_resource_types_use_full_arn_in_aws_identifier(self, scanner):
|
|
650
|
+
"""Test that all resource type parsers use full ARN in aws_identifier.
|
|
651
|
+
|
|
652
|
+
This ensures consistency across all AWS resource types.
|
|
653
|
+
"""
|
|
654
|
+
test_cases = [
|
|
655
|
+
{
|
|
656
|
+
"type": "AwsEc2Instance",
|
|
657
|
+
"arn": "arn:aws:ec2:us-east-1:123456789012:instance/i-1234567890abcdef0",
|
|
658
|
+
"details_key": "AwsEc2Instance",
|
|
659
|
+
"details": {"InstanceId": "i-1234567890abcdef0", "Type": "t3.medium"},
|
|
660
|
+
},
|
|
661
|
+
{
|
|
662
|
+
"type": "AwsEc2SecurityGroup",
|
|
663
|
+
"arn": "arn:aws:ec2:us-east-1:123456789012:security-group/sg-12345678",
|
|
664
|
+
"details_key": "AwsEc2SecurityGroup",
|
|
665
|
+
"details": {"GroupId": "sg-12345678", "GroupName": "test-sg"},
|
|
666
|
+
},
|
|
667
|
+
{
|
|
668
|
+
"type": "AwsEc2Subnet",
|
|
669
|
+
"arn": "arn:aws:ec2:us-east-1:123456789012:subnet/subnet-12345678",
|
|
670
|
+
"details_key": "AwsEc2Subnet",
|
|
671
|
+
"details": {"SubnetId": "subnet-12345678", "CidrBlock": "10.0.1.0/24"},
|
|
672
|
+
},
|
|
673
|
+
{
|
|
674
|
+
"type": "AwsIamUser",
|
|
675
|
+
"arn": "arn:aws:iam::123456789012:user/test-user",
|
|
676
|
+
"details_key": "AwsIamUser",
|
|
677
|
+
"details": {"UserName": "test-user"},
|
|
678
|
+
},
|
|
679
|
+
{
|
|
680
|
+
"type": "AwsRdsDbInstance",
|
|
681
|
+
"arn": "arn:aws:rds:us-east-1:123456789012:db:test-db",
|
|
682
|
+
"details_key": "AwsRdsDbInstance",
|
|
683
|
+
"details": {"DbInstanceIdentifier": "test-db", "Engine": "postgres"},
|
|
684
|
+
},
|
|
685
|
+
{
|
|
686
|
+
"type": "AwsLambdaFunction",
|
|
687
|
+
"arn": "arn:aws:lambda:us-east-1:123456789012:function:test-function",
|
|
688
|
+
"details_key": "AwsLambdaFunction",
|
|
689
|
+
"details": {"FunctionName": "test-function", "Runtime": "python3.9"},
|
|
690
|
+
},
|
|
691
|
+
{
|
|
692
|
+
"type": "AwsEcrRepository",
|
|
693
|
+
"arn": "arn:aws:ecr:us-east-1:123456789012:repository/test-repo",
|
|
694
|
+
"details_key": "AwsEcrRepository",
|
|
695
|
+
"details": {"RepositoryName": "test-repo"},
|
|
696
|
+
},
|
|
697
|
+
]
|
|
698
|
+
|
|
699
|
+
for test_case in test_cases:
|
|
700
|
+
resource = {
|
|
701
|
+
"Type": test_case["type"],
|
|
702
|
+
"Id": test_case["arn"],
|
|
703
|
+
"Region": "us-east-1",
|
|
704
|
+
"Details": {test_case["details_key"]: test_case["details"]},
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
finding_data = {
|
|
708
|
+
"Id": "arn:aws:securityhub:us-east-1:123456789012:finding/12345",
|
|
709
|
+
"CreatedAt": "2024-01-15T10:30:00.000Z",
|
|
710
|
+
"UpdatedAt": "2024-01-15T10:30:00.000Z",
|
|
711
|
+
"Severity": {"Label": "MEDIUM"},
|
|
712
|
+
"Title": "Test",
|
|
713
|
+
"Description": "Test",
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
asset = scanner.parse_resource_to_asset(resource, finding_data)
|
|
717
|
+
|
|
718
|
+
assert asset is not None, f"Asset should be created for {test_case['type']}"
|
|
719
|
+
assert asset.aws_identifier == test_case["arn"], (
|
|
720
|
+
f"{test_case['type']}: aws_identifier should be full ARN. "
|
|
721
|
+
f"Expected: {test_case['arn']}, Got: {asset.aws_identifier}"
|
|
722
|
+
)
|