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.

Files changed (112) hide show
  1. regscale/_version.py +1 -1
  2. regscale/core/app/utils/app_utils.py +11 -2
  3. regscale/dev/cli.py +26 -0
  4. regscale/dev/version.py +72 -0
  5. regscale/integrations/commercial/__init__.py +15 -1
  6. regscale/integrations/commercial/amazon/amazon/__init__.py +0 -0
  7. regscale/integrations/commercial/amazon/amazon/common.py +204 -0
  8. regscale/integrations/commercial/amazon/common.py +48 -58
  9. regscale/integrations/commercial/aws/audit_manager_compliance.py +2671 -0
  10. regscale/integrations/commercial/aws/cli.py +3093 -55
  11. regscale/integrations/commercial/aws/cloudtrail_control_mappings.py +333 -0
  12. regscale/integrations/commercial/aws/cloudtrail_evidence.py +501 -0
  13. regscale/integrations/commercial/aws/cloudwatch_control_mappings.py +357 -0
  14. regscale/integrations/commercial/aws/cloudwatch_evidence.py +490 -0
  15. regscale/integrations/commercial/aws/config_compliance.py +914 -0
  16. regscale/integrations/commercial/aws/conformance_pack_mappings.py +198 -0
  17. regscale/integrations/commercial/aws/evidence_generator.py +283 -0
  18. regscale/integrations/commercial/aws/guardduty_control_mappings.py +340 -0
  19. regscale/integrations/commercial/aws/guardduty_evidence.py +1053 -0
  20. regscale/integrations/commercial/aws/iam_control_mappings.py +368 -0
  21. regscale/integrations/commercial/aws/iam_evidence.py +574 -0
  22. regscale/integrations/commercial/aws/inventory/__init__.py +223 -22
  23. regscale/integrations/commercial/aws/inventory/base.py +107 -5
  24. regscale/integrations/commercial/aws/inventory/resources/audit_manager.py +513 -0
  25. regscale/integrations/commercial/aws/inventory/resources/cloudtrail.py +315 -0
  26. regscale/integrations/commercial/aws/inventory/resources/cloudtrail_logs_metadata.py +476 -0
  27. regscale/integrations/commercial/aws/inventory/resources/cloudwatch.py +191 -0
  28. regscale/integrations/commercial/aws/inventory/resources/compute.py +66 -9
  29. regscale/integrations/commercial/aws/inventory/resources/config.py +464 -0
  30. regscale/integrations/commercial/aws/inventory/resources/containers.py +74 -9
  31. regscale/integrations/commercial/aws/inventory/resources/database.py +106 -31
  32. regscale/integrations/commercial/aws/inventory/resources/guardduty.py +286 -0
  33. regscale/integrations/commercial/aws/inventory/resources/iam.py +470 -0
  34. regscale/integrations/commercial/aws/inventory/resources/inspector.py +476 -0
  35. regscale/integrations/commercial/aws/inventory/resources/integration.py +175 -61
  36. regscale/integrations/commercial/aws/inventory/resources/kms.py +447 -0
  37. regscale/integrations/commercial/aws/inventory/resources/networking.py +103 -67
  38. regscale/integrations/commercial/aws/inventory/resources/s3.py +394 -0
  39. regscale/integrations/commercial/aws/inventory/resources/security.py +268 -72
  40. regscale/integrations/commercial/aws/inventory/resources/securityhub.py +473 -0
  41. regscale/integrations/commercial/aws/inventory/resources/storage.py +53 -29
  42. regscale/integrations/commercial/aws/inventory/resources/systems_manager.py +657 -0
  43. regscale/integrations/commercial/aws/inventory/resources/vpc.py +655 -0
  44. regscale/integrations/commercial/aws/kms_control_mappings.py +288 -0
  45. regscale/integrations/commercial/aws/kms_evidence.py +879 -0
  46. regscale/integrations/commercial/aws/ocsf/__init__.py +7 -0
  47. regscale/integrations/commercial/aws/ocsf/constants.py +115 -0
  48. regscale/integrations/commercial/aws/ocsf/mapper.py +435 -0
  49. regscale/integrations/commercial/aws/org_control_mappings.py +286 -0
  50. regscale/integrations/commercial/aws/org_evidence.py +666 -0
  51. regscale/integrations/commercial/aws/s3_control_mappings.py +356 -0
  52. regscale/integrations/commercial/aws/s3_evidence.py +632 -0
  53. regscale/integrations/commercial/aws/scanner.py +851 -206
  54. regscale/integrations/commercial/aws/security_hub.py +319 -0
  55. regscale/integrations/commercial/aws/session_manager.py +282 -0
  56. regscale/integrations/commercial/aws/ssm_control_mappings.py +291 -0
  57. regscale/integrations/commercial/aws/ssm_evidence.py +492 -0
  58. regscale/integrations/compliance_integration.py +308 -38
  59. regscale/integrations/due_date_handler.py +3 -0
  60. regscale/integrations/scanner_integration.py +399 -84
  61. regscale/models/integration_models/cisa_kev_data.json +34 -4
  62. regscale/models/integration_models/synqly_models/capabilities.json +1 -1
  63. regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +17 -9
  64. regscale/models/regscale_models/assessment.py +2 -1
  65. regscale/models/regscale_models/control_objective.py +74 -5
  66. regscale/models/regscale_models/file.py +2 -0
  67. regscale/models/regscale_models/issue.py +2 -5
  68. {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/METADATA +1 -1
  69. {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/RECORD +112 -33
  70. tests/regscale/integrations/commercial/aws/__init__.py +0 -0
  71. tests/regscale/integrations/commercial/aws/test_audit_manager_compliance.py +1304 -0
  72. tests/regscale/integrations/commercial/aws/test_audit_manager_evidence_aggregation.py +341 -0
  73. tests/regscale/integrations/commercial/aws/test_aws_audit_manager_collector.py +1155 -0
  74. tests/regscale/integrations/commercial/aws/test_aws_cloudtrail_collector.py +534 -0
  75. tests/regscale/integrations/commercial/aws/test_aws_config_collector.py +400 -0
  76. tests/regscale/integrations/commercial/aws/test_aws_guardduty_collector.py +315 -0
  77. tests/regscale/integrations/commercial/aws/test_aws_iam_collector.py +458 -0
  78. tests/regscale/integrations/commercial/aws/test_aws_inspector_collector.py +353 -0
  79. tests/regscale/integrations/commercial/aws/test_aws_inventory_integration.py +530 -0
  80. tests/regscale/integrations/commercial/aws/test_aws_kms_collector.py +919 -0
  81. tests/regscale/integrations/commercial/aws/test_aws_s3_collector.py +722 -0
  82. tests/regscale/integrations/commercial/aws/test_aws_scanner_integration.py +722 -0
  83. tests/regscale/integrations/commercial/aws/test_aws_securityhub_collector.py +792 -0
  84. tests/regscale/integrations/commercial/aws/test_aws_systems_manager_collector.py +918 -0
  85. tests/regscale/integrations/commercial/aws/test_aws_vpc_collector.py +996 -0
  86. tests/regscale/integrations/commercial/aws/test_cli_evidence.py +431 -0
  87. tests/regscale/integrations/commercial/aws/test_cloudtrail_control_mappings.py +452 -0
  88. tests/regscale/integrations/commercial/aws/test_cloudtrail_evidence.py +788 -0
  89. tests/regscale/integrations/commercial/aws/test_config_compliance.py +298 -0
  90. tests/regscale/integrations/commercial/aws/test_conformance_pack_mappings.py +200 -0
  91. tests/regscale/integrations/commercial/aws/test_evidence_generator.py +386 -0
  92. tests/regscale/integrations/commercial/aws/test_guardduty_control_mappings.py +564 -0
  93. tests/regscale/integrations/commercial/aws/test_guardduty_evidence.py +1041 -0
  94. tests/regscale/integrations/commercial/aws/test_iam_control_mappings.py +718 -0
  95. tests/regscale/integrations/commercial/aws/test_iam_evidence.py +1375 -0
  96. tests/regscale/integrations/commercial/aws/test_kms_control_mappings.py +656 -0
  97. tests/regscale/integrations/commercial/aws/test_kms_evidence.py +1163 -0
  98. tests/regscale/integrations/commercial/aws/test_ocsf_mapper.py +370 -0
  99. tests/regscale/integrations/commercial/aws/test_org_control_mappings.py +546 -0
  100. tests/regscale/integrations/commercial/aws/test_org_evidence.py +1240 -0
  101. tests/regscale/integrations/commercial/aws/test_s3_control_mappings.py +672 -0
  102. tests/regscale/integrations/commercial/aws/test_s3_evidence.py +987 -0
  103. tests/regscale/integrations/commercial/aws/test_scanner_evidence.py +373 -0
  104. tests/regscale/integrations/commercial/aws/test_security_hub_config_filtering.py +539 -0
  105. tests/regscale/integrations/commercial/aws/test_session_manager.py +516 -0
  106. tests/regscale/integrations/commercial/aws/test_ssm_control_mappings.py +588 -0
  107. tests/regscale/integrations/commercial/aws/test_ssm_evidence.py +735 -0
  108. tests/regscale/integrations/commercial/test_aws.py +55 -56
  109. {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/LICENSE +0 -0
  110. {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/WHEEL +0 -0
  111. {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/entry_points.txt +0 -0
  112. {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
+ )