regscale-cli 6.27.3.0__py3-none-any.whl → 6.28.1.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 (113) 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/commercial/synqly/ticketing.py +27 -0
  59. regscale/integrations/compliance_integration.py +308 -38
  60. regscale/integrations/due_date_handler.py +3 -0
  61. regscale/integrations/scanner_integration.py +399 -84
  62. regscale/models/integration_models/cisa_kev_data.json +65 -5
  63. regscale/models/integration_models/synqly_models/capabilities.json +1 -1
  64. regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +17 -9
  65. regscale/models/regscale_models/assessment.py +2 -1
  66. regscale/models/regscale_models/control_objective.py +74 -5
  67. regscale/models/regscale_models/file.py +2 -0
  68. regscale/models/regscale_models/issue.py +2 -5
  69. {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.1.0.dist-info}/METADATA +1 -1
  70. {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.1.0.dist-info}/RECORD +113 -34
  71. tests/regscale/integrations/commercial/aws/__init__.py +0 -0
  72. tests/regscale/integrations/commercial/aws/test_audit_manager_compliance.py +1304 -0
  73. tests/regscale/integrations/commercial/aws/test_audit_manager_evidence_aggregation.py +341 -0
  74. tests/regscale/integrations/commercial/aws/test_aws_audit_manager_collector.py +1155 -0
  75. tests/regscale/integrations/commercial/aws/test_aws_cloudtrail_collector.py +534 -0
  76. tests/regscale/integrations/commercial/aws/test_aws_config_collector.py +400 -0
  77. tests/regscale/integrations/commercial/aws/test_aws_guardduty_collector.py +315 -0
  78. tests/regscale/integrations/commercial/aws/test_aws_iam_collector.py +458 -0
  79. tests/regscale/integrations/commercial/aws/test_aws_inspector_collector.py +353 -0
  80. tests/regscale/integrations/commercial/aws/test_aws_inventory_integration.py +530 -0
  81. tests/regscale/integrations/commercial/aws/test_aws_kms_collector.py +919 -0
  82. tests/regscale/integrations/commercial/aws/test_aws_s3_collector.py +722 -0
  83. tests/regscale/integrations/commercial/aws/test_aws_scanner_integration.py +722 -0
  84. tests/regscale/integrations/commercial/aws/test_aws_securityhub_collector.py +792 -0
  85. tests/regscale/integrations/commercial/aws/test_aws_systems_manager_collector.py +918 -0
  86. tests/regscale/integrations/commercial/aws/test_aws_vpc_collector.py +996 -0
  87. tests/regscale/integrations/commercial/aws/test_cli_evidence.py +431 -0
  88. tests/regscale/integrations/commercial/aws/test_cloudtrail_control_mappings.py +452 -0
  89. tests/regscale/integrations/commercial/aws/test_cloudtrail_evidence.py +788 -0
  90. tests/regscale/integrations/commercial/aws/test_config_compliance.py +298 -0
  91. tests/regscale/integrations/commercial/aws/test_conformance_pack_mappings.py +200 -0
  92. tests/regscale/integrations/commercial/aws/test_evidence_generator.py +386 -0
  93. tests/regscale/integrations/commercial/aws/test_guardduty_control_mappings.py +564 -0
  94. tests/regscale/integrations/commercial/aws/test_guardduty_evidence.py +1041 -0
  95. tests/regscale/integrations/commercial/aws/test_iam_control_mappings.py +718 -0
  96. tests/regscale/integrations/commercial/aws/test_iam_evidence.py +1375 -0
  97. tests/regscale/integrations/commercial/aws/test_kms_control_mappings.py +656 -0
  98. tests/regscale/integrations/commercial/aws/test_kms_evidence.py +1163 -0
  99. tests/regscale/integrations/commercial/aws/test_ocsf_mapper.py +370 -0
  100. tests/regscale/integrations/commercial/aws/test_org_control_mappings.py +546 -0
  101. tests/regscale/integrations/commercial/aws/test_org_evidence.py +1240 -0
  102. tests/regscale/integrations/commercial/aws/test_s3_control_mappings.py +672 -0
  103. tests/regscale/integrations/commercial/aws/test_s3_evidence.py +987 -0
  104. tests/regscale/integrations/commercial/aws/test_scanner_evidence.py +373 -0
  105. tests/regscale/integrations/commercial/aws/test_security_hub_config_filtering.py +539 -0
  106. tests/regscale/integrations/commercial/aws/test_session_manager.py +516 -0
  107. tests/regscale/integrations/commercial/aws/test_ssm_control_mappings.py +588 -0
  108. tests/regscale/integrations/commercial/aws/test_ssm_evidence.py +735 -0
  109. tests/regscale/integrations/commercial/test_aws.py +55 -56
  110. {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.1.0.dist-info}/LICENSE +0 -0
  111. {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.1.0.dist-info}/WHEEL +0 -0
  112. {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.1.0.dist-info}/entry_points.txt +0 -0
  113. {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.1.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,530 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Integration tests for AWS inventory 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
13
+ from regscale.models import regscale_models
14
+
15
+ PLAN_ID = 36
16
+
17
+
18
+ @pytest.mark.integration
19
+ class TestAWSInventoryIntegration:
20
+ """Test suite for AWS inventory integration with mocked AWS responses.
21
+
22
+ These are integration tests that validate the AWS inventory collection
23
+ with simulated data. They test the full workflow of fetching inventory from
24
+ AWS 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
+ "aws": {
33
+ "inventory": {
34
+ "enabled_services": {
35
+ "ec2": True,
36
+ "s3": True,
37
+ "rds": True,
38
+ "lambda": True,
39
+ "dynamodb": True,
40
+ "vpc": True,
41
+ "elb": True,
42
+ "ecr": True,
43
+ }
44
+ }
45
+ }
46
+ }
47
+ return app
48
+
49
+ @pytest.fixture
50
+ def scanner(self, mock_regscale_app):
51
+ """Create an AWS scanner instance."""
52
+ scanner = AWSInventoryIntegration(plan_id=PLAN_ID)
53
+ scanner.app = mock_regscale_app
54
+ return scanner
55
+
56
+ @pytest.fixture
57
+ def mock_aws_inventory(self):
58
+ """Create realistic mock AWS inventory data."""
59
+ return {
60
+ "EC2Instances": [
61
+ {
62
+ "InstanceId": "i-1234567890abcdef0",
63
+ "InstanceType": "t3.medium",
64
+ "State": "running",
65
+ "Region": "us-east-1",
66
+ "PrivateIpAddress": "10.0.1.100",
67
+ "PublicIpAddress": "54.123.45.67",
68
+ "VpcId": "vpc-12345678",
69
+ "SubnetId": "subnet-12345678",
70
+ "ImageId": "ami-0abcdef1234567890",
71
+ "Platform": None,
72
+ "PlatformDetails": "Linux/UNIX",
73
+ "PublicDnsName": "ec2-54-123-45-67.compute-1.amazonaws.com",
74
+ "PrivateDnsName": "ip-10-0-1-100.ec2.internal",
75
+ "Tags": [{"Key": "Name", "Value": "WebServer-01"}],
76
+ "ImageInfo": {
77
+ "Name": "amazon-linux-2",
78
+ "Description": "Amazon Linux 2 AMI",
79
+ "RootDeviceType": "ebs",
80
+ "VirtualizationType": "hvm",
81
+ },
82
+ "CpuOptions": {"CoreCount": 1, "ThreadsPerCore": 2},
83
+ "BlockDeviceMappings": [{"Ebs": {"VolumeId": "vol-12345678"}}],
84
+ }
85
+ ],
86
+ "S3Buckets": [
87
+ {
88
+ "Name": "my-app-bucket",
89
+ "Region": "us-east-1",
90
+ "CreationDate": "2024-01-01T00:00:00.000Z",
91
+ "Grants": [],
92
+ }
93
+ ],
94
+ "RDSInstances": [
95
+ {
96
+ "DBInstanceIdentifier": "my-database",
97
+ "DBInstanceClass": "db.t3.micro",
98
+ "Engine": "postgres",
99
+ "EngineVersion": "14.7",
100
+ "DBInstanceStatus": "available",
101
+ "AvailabilityZone": "us-east-1a",
102
+ "Endpoint": {"Address": "my-database.abcdef.us-east-1.rds.amazonaws.com"},
103
+ "VpcId": "vpc-12345678",
104
+ "PubliclyAccessible": False,
105
+ "AllocatedStorage": 20,
106
+ "DBInstanceArn": "arn:aws:rds:us-east-1:123456789012:db:my-database",
107
+ }
108
+ ],
109
+ "LambdaFunctions": [
110
+ {
111
+ "FunctionName": "my-lambda-function",
112
+ "FunctionArn": "arn:aws:lambda:us-east-1:123456789012:function:my-lambda-function",
113
+ "Runtime": "python3.9",
114
+ "MemorySize": 256,
115
+ "Timeout": 30,
116
+ "Handler": "index.handler",
117
+ "Description": "Processes incoming events",
118
+ "Region": "us-east-1",
119
+ }
120
+ ],
121
+ "DynamoDBTables": [
122
+ {
123
+ "TableName": "users-table",
124
+ "TableStatus": "ACTIVE",
125
+ "TableArn": "arn:aws:dynamodb:us-east-1:123456789012:table/users-table",
126
+ "TableSizeBytes": 1024000,
127
+ "Region": "us-east-1",
128
+ }
129
+ ],
130
+ "VPCs": [
131
+ {
132
+ "VpcId": "vpc-12345678",
133
+ "CidrBlock": "10.0.0.0/16",
134
+ "State": "available",
135
+ "IsDefault": False,
136
+ "Region": "us-east-1",
137
+ "Tags": [{"Key": "Name", "Value": "main-vpc"}],
138
+ }
139
+ ],
140
+ "LoadBalancers": [
141
+ {
142
+ "LoadBalancerName": "my-load-balancer",
143
+ "LoadBalancerArn": "arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188",
144
+ "DNSName": "my-load-balancer-1234567890.us-east-1.elb.amazonaws.com",
145
+ "Scheme": "internet-facing",
146
+ "State": "active",
147
+ "VpcId": "vpc-12345678",
148
+ "Region": "us-east-1",
149
+ "Listeners": [
150
+ {"Port": 80, "Protocol": "HTTP"},
151
+ {"Port": 443, "Protocol": "HTTPS"},
152
+ ],
153
+ }
154
+ ],
155
+ "ECRRepositories": [
156
+ {
157
+ "RepositoryName": "my-app",
158
+ "RepositoryArn": "arn:aws:ecr:us-east-1:123456789012:repository/my-app",
159
+ "RepositoryUri": "123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app",
160
+ "ImageTagMutability": "MUTABLE",
161
+ "ImageScanningConfiguration": {"ScanOnPush": True},
162
+ "Region": "us-east-1",
163
+ }
164
+ ],
165
+ "IAM": {
166
+ "Roles": [
167
+ {
168
+ "Arn": "arn:aws:iam::123456789012:role/AdminRole",
169
+ "RoleName": "AdminRole",
170
+ "CreateDate": "2024-01-01T00:00:00.000Z",
171
+ }
172
+ ]
173
+ },
174
+ }
175
+
176
+ def test_sync_assets_only(self, scanner, mock_aws_inventory):
177
+ """Test syncing only AWS assets without findings."""
178
+ # Mock the fetch_aws_data_if_needed to return our test inventory
179
+ with patch.object(scanner, "fetch_aws_data_if_needed") as mock_fetch:
180
+ mock_fetch.return_value = mock_aws_inventory
181
+
182
+ # Call fetch_assets to get the assets
183
+ assets_iterator = scanner.fetch_assets(
184
+ region="us-east-1",
185
+ profile="test-profile",
186
+ aws_access_key_id=None,
187
+ aws_secret_access_key=None,
188
+ aws_session_token=None,
189
+ )
190
+
191
+ # Convert iterator to list
192
+ assets_list = list(assets_iterator)
193
+
194
+ # Should have 9 assets (1 EC2, 1 S3, 1 RDS, 1 Lambda, 1 DynamoDB, 1 VPC, 1 LB, 1 ECR, 1 IAM)
195
+ assert len(assets_list) == 9
196
+
197
+ # Verify all assets are IntegrationAsset objects
198
+ for asset in assets_list:
199
+ assert isinstance(asset, IntegrationAsset)
200
+ assert asset.parent_id == PLAN_ID
201
+
202
+ def test_fetch_assets(self, scanner, mock_aws_inventory):
203
+ """Test that fetch_assets correctly retrieves and parses AWS inventory."""
204
+ # Mock the fetch_aws_data_if_needed to return our test inventory
205
+ with patch.object(scanner, "fetch_aws_data_if_needed") as mock_fetch:
206
+ mock_fetch.return_value = mock_aws_inventory
207
+
208
+ # Call fetch_assets to get parsed assets
209
+ assets_iterator = scanner.fetch_assets(
210
+ region="us-east-1",
211
+ profile="test-profile",
212
+ aws_access_key_id=None,
213
+ aws_secret_access_key=None,
214
+ aws_session_token=None,
215
+ )
216
+
217
+ # Convert iterator to list
218
+ assets_list = list(assets_iterator)
219
+
220
+ # Should have 9 assets
221
+ assert len(assets_list) == 9, f"Expected 9 assets but got {len(assets_list)}"
222
+
223
+ # Verify assets were properly parsed
224
+ for asset in assets_list:
225
+ assert isinstance(asset, IntegrationAsset)
226
+ assert asset.name is not None
227
+ assert asset.identifier is not None
228
+ assert asset.manufacturer == "AWS"
229
+
230
+ def test_parse_ec2_instance(self, scanner, mock_aws_inventory):
231
+ """Test parsing EC2 instance to IntegrationAsset."""
232
+ ec2_data = mock_aws_inventory["EC2Instances"][0]
233
+ asset = scanner.parse_ec2_instance(ec2_data)
234
+
235
+ assert isinstance(asset, IntegrationAsset)
236
+ assert asset.name == "WebServer-01"
237
+ assert asset.identifier == "i-1234567890abcdef0"
238
+ assert asset.asset_type == regscale_models.AssetType.VM
239
+ assert asset.manufacturer == "AWS"
240
+ assert asset.model == "t3.medium"
241
+ assert asset.aws_identifier == "arn:aws:ec2:us-east-1::instance/i-1234567890abcdef0"
242
+ assert asset.is_virtual is True
243
+ assert asset.ip_address == "10.0.1.100"
244
+ assert asset.location == "us-east-1"
245
+ assert asset.status == regscale_models.AssetStatus.Active
246
+
247
+ def test_parse_s3_bucket(self, scanner, mock_aws_inventory):
248
+ """Test parsing S3 bucket to IntegrationAsset."""
249
+ s3_data = mock_aws_inventory["S3Buckets"][0]
250
+ asset = scanner.parse_s3_bucket(s3_data)
251
+
252
+ assert isinstance(asset, IntegrationAsset)
253
+ assert asset.name == "my-app-bucket"
254
+ assert asset.identifier == "my-app-bucket"
255
+ assert asset.asset_type == regscale_models.AssetType.Other
256
+ assert asset.manufacturer == "AWS"
257
+ assert asset.aws_identifier == "arn:aws:s3:::my-app-bucket"
258
+ assert asset.location == "us-east-1"
259
+
260
+ def test_parse_rds_instance(self, scanner, mock_aws_inventory):
261
+ """Test parsing RDS instance to IntegrationAsset."""
262
+ rds_data = mock_aws_inventory["RDSInstances"][0]
263
+ asset = scanner.parse_rds_instance(rds_data)
264
+
265
+ assert isinstance(asset, IntegrationAsset)
266
+ assert "my-database" in asset.name
267
+ assert asset.identifier == "my-database"
268
+ assert asset.asset_type == regscale_models.AssetType.VM
269
+ assert asset.manufacturer == "AWS"
270
+ assert asset.model == "db.t3.micro"
271
+ assert asset.software_name == "postgres"
272
+ assert asset.software_version == "14.7"
273
+ assert asset.aws_identifier == "arn:aws:rds:us-east-1:123456789012:db:my-database"
274
+ assert asset.is_public_facing is False
275
+ assert asset.disk_storage == 20
276
+
277
+ def test_parse_lambda_function(self, scanner, mock_aws_inventory):
278
+ """Test parsing Lambda function to IntegrationAsset."""
279
+ lambda_data = mock_aws_inventory["LambdaFunctions"][0]
280
+ asset = scanner.parse_lambda_function(lambda_data)
281
+
282
+ assert isinstance(asset, IntegrationAsset)
283
+ assert asset.name == "my-lambda-function"
284
+ assert asset.identifier == "my-lambda-function"
285
+ assert asset.asset_type == regscale_models.AssetType.Other
286
+ assert asset.manufacturer == "AWS"
287
+ assert asset.software_name == "python3.9"
288
+ assert asset.ram == 256
289
+ assert asset.aws_identifier == "arn:aws:lambda:us-east-1:123456789012:function:my-lambda-function"
290
+ assert asset.is_virtual is True
291
+
292
+ def test_parse_dynamodb_table(self, scanner, mock_aws_inventory):
293
+ """Test parsing DynamoDB table to IntegrationAsset."""
294
+ dynamodb_data = mock_aws_inventory["DynamoDBTables"][0]
295
+ asset = scanner.parse_dynamodb_table(dynamodb_data)
296
+
297
+ assert isinstance(asset, IntegrationAsset)
298
+ assert "users-table" in asset.name
299
+ assert asset.identifier == "users-table"
300
+ assert asset.asset_type == regscale_models.AssetType.Other
301
+ assert asset.manufacturer == "AWS"
302
+ assert asset.aws_identifier == "arn:aws:dynamodb:us-east-1:123456789012:table/users-table"
303
+ assert asset.disk_storage == 1024000
304
+ assert asset.status == regscale_models.AssetStatus.Active
305
+
306
+ def test_parse_vpc(self, scanner, mock_aws_inventory):
307
+ """Test parsing VPC to IntegrationAsset."""
308
+ vpc_data = mock_aws_inventory["VPCs"][0]
309
+ asset = scanner.parse_vpc(vpc_data)
310
+
311
+ assert isinstance(asset, IntegrationAsset)
312
+ assert asset.name == "main-vpc"
313
+ assert asset.identifier == "vpc-12345678"
314
+ assert asset.asset_type == regscale_models.AssetType.NetworkRouter
315
+ assert asset.manufacturer == "AWS"
316
+ assert asset.aws_identifier == "arn:aws:ec2:us-east-1::vpc/vpc-12345678"
317
+ assert asset.vlan_id == "vpc-12345678"
318
+ assert asset.status == regscale_models.AssetStatus.Active
319
+ assert "10.0.0.0/16" in asset.notes
320
+
321
+ def test_parse_load_balancer(self, scanner, mock_aws_inventory):
322
+ """Test parsing Load Balancer to IntegrationAsset."""
323
+ lb_data = mock_aws_inventory["LoadBalancers"][0]
324
+ asset = scanner.parse_load_balancer(lb_data)
325
+
326
+ assert isinstance(asset, IntegrationAsset)
327
+ assert asset.name == "my-load-balancer"
328
+ assert asset.identifier == "my-load-balancer"
329
+ assert asset.asset_type == regscale_models.AssetType.NetworkRouter
330
+ assert asset.manufacturer == "AWS"
331
+ assert (
332
+ asset.aws_identifier
333
+ == "arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188"
334
+ )
335
+ assert asset.fqdn == "my-load-balancer-1234567890.us-east-1.elb.amazonaws.com"
336
+ assert asset.is_public_facing is True
337
+ assert asset.status == regscale_models.AssetStatus.Active
338
+ assert len(asset.ports_and_protocols) == 2
339
+
340
+ def test_parse_ecr_repository(self, scanner, mock_aws_inventory):
341
+ """Test parsing ECR repository to IntegrationAsset."""
342
+ ecr_data = mock_aws_inventory["ECRRepositories"][0]
343
+ asset = scanner.parse_ecr_repository(ecr_data)
344
+
345
+ assert isinstance(asset, IntegrationAsset)
346
+ assert asset.name == "my-app"
347
+ assert asset.identifier == "my-app"
348
+ assert asset.asset_type == regscale_models.AssetType.Other
349
+ assert asset.manufacturer == "AWS"
350
+ assert asset.aws_identifier.startswith("arn:aws:ecr:us-east-1")
351
+ assert asset.uri == "123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app"
352
+ # Note: notes field is no longer populated by implementation
353
+
354
+ def test_parse_aws_account(self, scanner, mock_aws_inventory):
355
+ """Test parsing IAM role to AWS Account asset."""
356
+ iam_data = mock_aws_inventory["IAM"]["Roles"][0]
357
+ asset = scanner.parse_aws_account(iam_data)
358
+
359
+ assert isinstance(asset, IntegrationAsset)
360
+ assert asset.name == "123456789012"
361
+ assert asset.identifier == "AWS::::Account:123456789012"
362
+ assert asset.asset_type == regscale_models.AssetType.Other
363
+ assert asset.manufacturer == "AWS"
364
+ assert asset.aws_identifier == "AWS::::Account:123456789012"
365
+
366
+ def test_fetch_assets_with_empty_inventory(self, scanner):
367
+ """Test fetch_assets when no resources are returned from AWS."""
368
+ empty_inventory = {
369
+ "EC2Instances": [],
370
+ "S3Buckets": [],
371
+ "RDSInstances": [],
372
+ "LambdaFunctions": [],
373
+ "DynamoDBTables": [],
374
+ "VPCs": [],
375
+ "LoadBalancers": [],
376
+ "ECRRepositories": [],
377
+ "IAM": {"Roles": []},
378
+ }
379
+
380
+ with patch.object(scanner, "fetch_aws_data_if_needed") as mock_fetch:
381
+ mock_fetch.return_value = empty_inventory
382
+
383
+ assets = list(
384
+ scanner.fetch_assets(
385
+ region="us-east-1",
386
+ profile="test",
387
+ aws_access_key_id=None,
388
+ aws_secret_access_key=None,
389
+ aws_session_token=None,
390
+ )
391
+ )
392
+
393
+ assert len(assets) == 0
394
+ assert scanner.num_assets_to_process == 0
395
+
396
+ def test_inventory_with_authentication_error(self, scanner):
397
+ """Test fetch_assets handles authentication errors gracefully."""
398
+ with patch.object(scanner, "authenticate") as mock_auth:
399
+ mock_auth.side_effect = Exception("The security token included in the request is invalid")
400
+
401
+ with patch.object(scanner, "fetch_aws_data_if_needed") as mock_fetch:
402
+ mock_fetch.side_effect = Exception("The security token included in the request is invalid")
403
+
404
+ with pytest.raises(Exception) as exc_info:
405
+ list(
406
+ scanner.fetch_assets(
407
+ region="us-east-1",
408
+ profile="test",
409
+ aws_access_key_id=None,
410
+ aws_secret_access_key=None,
411
+ aws_session_token=None,
412
+ )
413
+ )
414
+
415
+ assert "security token" in str(exc_info.value).lower()
416
+
417
+ def test_asset_counts_by_type(self, scanner, mock_aws_inventory):
418
+ """Test that each asset type is correctly counted and parsed."""
419
+ with patch.object(scanner, "fetch_aws_data_if_needed") as mock_fetch:
420
+ mock_fetch.return_value = mock_aws_inventory
421
+
422
+ assets_iterator = scanner.fetch_assets(
423
+ region="us-east-1",
424
+ profile="test-profile",
425
+ aws_access_key_id=None,
426
+ aws_secret_access_key=None,
427
+ aws_session_token=None,
428
+ )
429
+
430
+ assets_list = list(assets_iterator)
431
+
432
+ # Count assets by type
433
+ asset_types = {}
434
+ for asset in assets_list:
435
+ asset_type = asset.asset_type.name
436
+ asset_types[asset_type] = asset_types.get(asset_type, 0) + 1
437
+
438
+ # Verify counts
439
+ assert asset_types[regscale_models.AssetType.VM.name] == 2 # EC2 + RDS
440
+ assert asset_types[regscale_models.AssetType.Other.name] == 5 # S3 + Lambda + DynamoDB + ECR + IAM
441
+ assert asset_types[regscale_models.AssetType.NetworkRouter.name] == 2 # VPC + LB
442
+
443
+ def test_sync_assets_to_database(self, scanner, mock_aws_inventory, mock_regscale_app):
444
+ """Test syncing AWS assets to RegScale database.
445
+
446
+ This test validates the full end-to-end flow of:
447
+ 1. Fetching AWS inventory data
448
+ 2. Parsing it into IntegrationAsset objects
449
+ 3. Creating/updating assets in RegScale database via bulk operations
450
+
451
+ NOTE: The system uses bulk operations for efficiency, so assets are queued
452
+ and then saved all at once via Asset.bulk_save().
453
+ """
454
+ # Mock the fetch_aws_data_if_needed to return our test inventory
455
+ with patch.object(scanner, "fetch_aws_data_if_needed") as mock_fetch:
456
+ mock_fetch.return_value = mock_aws_inventory
457
+
458
+ # Mock the bulk save operation which is how assets are persisted
459
+ with patch.object(regscale_models.Asset, "bulk_save") as mock_bulk_save:
460
+ # Mock response: {'created': [...], 'updated': [...], 'created_count': 9, 'updated_count': 0}
461
+ mock_bulk_save.return_value = {
462
+ "created": [MagicMock(id=i) for i in range(1, 10)],
463
+ "updated": [],
464
+ "created_count": 9,
465
+ "updated_count": 0,
466
+ }
467
+
468
+ # Mock component creation
469
+ with patch.object(regscale_models.Component, "get_or_create") as mock_component_create:
470
+ mock_component = MagicMock()
471
+ mock_component.id = 456
472
+ mock_component.securityPlansId = PLAN_ID
473
+ mock_component_create.return_value = mock_component
474
+
475
+ # Mock asset mapping creation
476
+ with patch.object(regscale_models.AssetMapping, "get_or_create_with_status") as mock_asset_mapping:
477
+ mock_asset_mapping.return_value = (True, MagicMock())
478
+
479
+ # Mock asset cache population
480
+ with patch.object(regscale_models.Asset, "get_all_by_parent") as mock_get_all:
481
+ mock_get_all.return_value = []
482
+
483
+ # Mock other bulk operations
484
+ with patch.object(regscale_models.Issue, "bulk_save") as mock_issue_bulk:
485
+ mock_issue_bulk.return_value = {"created": [], "updated": []}
486
+
487
+ with patch.object(regscale_models.Property, "bulk_save") as mock_property_bulk:
488
+ mock_property_bulk.return_value = {"created": [], "updated": []}
489
+
490
+ with patch.object(regscale_models.Data, "bulk_save") as mock_data_bulk:
491
+ mock_data_bulk.return_value = {"created": [], "updated": []}
492
+
493
+ # Mock mapping cache population
494
+ with patch.object(regscale_models.AssetMapping, "populate_cache_by_plan"):
495
+ with patch.object(
496
+ regscale_models.ComponentMapping, "populate_cache_by_plan"
497
+ ):
498
+ # Fetch assets and sync them
499
+ assets_iterator = scanner.fetch_assets(
500
+ region="us-east-1",
501
+ profile="test-profile",
502
+ aws_access_key_id=None,
503
+ aws_secret_access_key=None,
504
+ aws_session_token=None,
505
+ )
506
+
507
+ # Call update_regscale_assets to persist to database
508
+ assets_processed = scanner.update_regscale_assets(
509
+ assets=assets_iterator
510
+ )
511
+
512
+ # Verify assets were processed
513
+ assert (
514
+ assets_processed == 9
515
+ ), f"Expected 9 assets processed but got {assets_processed}"
516
+
517
+ # Verify bulk_save was called once for assets
518
+ assert (
519
+ mock_bulk_save.call_count == 1
520
+ ), f"Expected Asset.bulk_save to be called once, but it was called {mock_bulk_save.call_count} times"
521
+
522
+ # Verify the scanner tracked the results correctly
523
+ # Note: Assets are created once per component name, so the count may be higher
524
+ # than the number of unique assets. For this test, we just verify bulk_save was called.
525
+ created_count = scanner._results.get("assets", {}).get(
526
+ "created_count", 0
527
+ )
528
+ assert (
529
+ created_count > 0
530
+ ), f"Expected assets to be created, but got {created_count}"