regscale-cli 6.21.2.0__py3-none-any.whl → 6.28.2.1__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.
- regscale/_version.py +1 -1
- regscale/airflow/hierarchy.py +2 -2
- regscale/core/app/api.py +5 -2
- regscale/core/app/application.py +36 -6
- regscale/core/app/internal/control_editor.py +73 -21
- regscale/core/app/internal/evidence.py +727 -204
- regscale/core/app/internal/login.py +4 -2
- regscale/core/app/internal/model_editor.py +219 -64
- regscale/core/app/utils/app_utils.py +86 -12
- regscale/core/app/utils/catalog_utils/common.py +1 -1
- regscale/core/login.py +21 -4
- regscale/core/utils/async_graphql_client.py +363 -0
- regscale/core/utils/date.py +77 -1
- regscale/dev/cli.py +26 -0
- regscale/dev/code_gen.py +109 -24
- regscale/dev/version.py +72 -0
- regscale/integrations/commercial/__init__.py +30 -2
- regscale/integrations/commercial/aws/audit_manager_compliance.py +3908 -0
- regscale/integrations/commercial/aws/cli.py +3107 -54
- 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/{amazon → aws}/common.py +71 -19
- regscale/integrations/commercial/aws/config_compliance.py +914 -0
- regscale/integrations/commercial/aws/conformance_pack_mappings.py +198 -0
- regscale/integrations/commercial/aws/control_compliance_analyzer.py +439 -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 +338 -22
- regscale/integrations/commercial/aws/inventory/base.py +107 -5
- regscale/integrations/commercial/aws/inventory/resources/analytics.py +390 -0
- regscale/integrations/commercial/aws/inventory/resources/applications.py +234 -0
- 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 +328 -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 +481 -31
- regscale/integrations/commercial/aws/inventory/resources/developer_tools.py +253 -0
- 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/machine_learning.py +358 -0
- regscale/integrations/commercial/aws/inventory/resources/networking.py +390 -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 +288 -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 +1072 -205
- 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/commercial/jira.py +489 -153
- regscale/integrations/commercial/microsoft_defender/defender.py +326 -5
- regscale/integrations/commercial/microsoft_defender/defender_api.py +348 -14
- regscale/integrations/commercial/microsoft_defender/defender_constants.py +157 -0
- regscale/integrations/commercial/qualys/__init__.py +167 -68
- regscale/integrations/commercial/qualys/scanner.py +305 -39
- regscale/integrations/commercial/sarif/sairf_importer.py +432 -0
- regscale/integrations/commercial/sarif/sarif_converter.py +67 -0
- regscale/integrations/commercial/sicura/api.py +79 -42
- regscale/integrations/commercial/sicura/commands.py +8 -2
- regscale/integrations/commercial/sicura/scanner.py +83 -44
- regscale/integrations/commercial/stigv2/ckl_parser.py +5 -5
- regscale/integrations/commercial/synqly/assets.py +133 -16
- regscale/integrations/commercial/synqly/edr.py +2 -8
- regscale/integrations/commercial/synqly/query_builder.py +536 -0
- regscale/integrations/commercial/synqly/ticketing.py +27 -0
- regscale/integrations/commercial/synqly/vulnerabilities.py +165 -28
- regscale/integrations/commercial/tenablev2/cis_parsers.py +453 -0
- regscale/integrations/commercial/tenablev2/cis_scanner.py +447 -0
- regscale/integrations/commercial/tenablev2/commands.py +146 -5
- regscale/integrations/commercial/tenablev2/scanner.py +1 -3
- regscale/integrations/commercial/tenablev2/stig_parsers.py +113 -57
- regscale/integrations/commercial/wizv2/WizDataMixin.py +1 -1
- regscale/integrations/commercial/wizv2/click.py +191 -76
- regscale/integrations/commercial/wizv2/compliance/__init__.py +15 -0
- regscale/integrations/commercial/wizv2/{policy_compliance_helpers.py → compliance/helpers.py} +78 -60
- regscale/integrations/commercial/wizv2/compliance_report.py +1592 -0
- regscale/integrations/commercial/wizv2/core/__init__.py +133 -0
- regscale/integrations/commercial/wizv2/{async_client.py → core/client.py} +7 -3
- regscale/integrations/commercial/wizv2/{constants.py → core/constants.py} +92 -89
- regscale/integrations/commercial/wizv2/core/file_operations.py +237 -0
- regscale/integrations/commercial/wizv2/fetchers/__init__.py +11 -0
- regscale/integrations/commercial/wizv2/{data_fetcher.py → fetchers/policy_assessment.py} +66 -9
- regscale/integrations/commercial/wizv2/file_cleanup.py +104 -0
- regscale/integrations/commercial/wizv2/issue.py +776 -28
- regscale/integrations/commercial/wizv2/models/__init__.py +0 -0
- regscale/integrations/commercial/wizv2/parsers/__init__.py +34 -0
- regscale/integrations/commercial/wizv2/{parsers.py → parsers/main.py} +1 -1
- regscale/integrations/commercial/wizv2/processors/__init__.py +11 -0
- regscale/integrations/commercial/wizv2/{finding_processor.py → processors/finding.py} +1 -1
- regscale/integrations/commercial/wizv2/reports.py +243 -0
- regscale/integrations/commercial/wizv2/sbom.py +1 -1
- regscale/integrations/commercial/wizv2/scanner.py +1031 -441
- regscale/integrations/commercial/wizv2/utils/__init__.py +48 -0
- regscale/integrations/commercial/wizv2/{utils.py → utils/main.py} +116 -61
- regscale/integrations/commercial/wizv2/variables.py +89 -3
- regscale/integrations/compliance_integration.py +1036 -151
- regscale/integrations/control_matcher.py +432 -0
- regscale/integrations/due_date_handler.py +333 -0
- regscale/integrations/milestone_manager.py +291 -0
- regscale/integrations/public/__init__.py +14 -0
- regscale/integrations/public/cci_importer.py +834 -0
- regscale/integrations/public/csam/__init__.py +0 -0
- regscale/integrations/public/csam/csam.py +938 -0
- regscale/integrations/public/csam/csam_agency_defined.py +179 -0
- regscale/integrations/public/csam/csam_common.py +154 -0
- regscale/integrations/public/csam/csam_controls.py +432 -0
- regscale/integrations/public/csam/csam_poam.py +124 -0
- regscale/integrations/public/fedramp/click.py +77 -6
- regscale/integrations/public/fedramp/docx_parser.py +10 -1
- regscale/integrations/public/fedramp/fedramp_cis_crm.py +675 -289
- regscale/integrations/public/fedramp/fedramp_five.py +1 -1
- regscale/integrations/public/fedramp/poam/scanner.py +75 -7
- regscale/integrations/public/fedramp/poam_export_v5.py +888 -0
- regscale/integrations/scanner_integration.py +1961 -430
- regscale/models/integration_models/CCI_List.xml +1 -0
- regscale/models/integration_models/aqua.py +2 -2
- regscale/models/integration_models/cisa_kev_data.json +805 -11
- regscale/models/integration_models/flat_file_importer/__init__.py +5 -8
- regscale/models/integration_models/nexpose.py +36 -10
- regscale/models/integration_models/qualys.py +3 -4
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +87 -18
- regscale/models/integration_models/synqly_models/filter_parser.py +332 -0
- regscale/models/integration_models/synqly_models/ocsf_mapper.py +124 -25
- regscale/models/integration_models/synqly_models/synqly_model.py +89 -16
- regscale/models/locking.py +12 -8
- regscale/models/platform.py +4 -2
- regscale/models/regscale_models/__init__.py +7 -0
- regscale/models/regscale_models/assessment.py +2 -1
- regscale/models/regscale_models/catalog.py +1 -1
- regscale/models/regscale_models/compliance_settings.py +251 -1
- regscale/models/regscale_models/component.py +1 -0
- regscale/models/regscale_models/control_implementation.py +236 -41
- regscale/models/regscale_models/control_objective.py +74 -5
- regscale/models/regscale_models/file.py +2 -0
- regscale/models/regscale_models/form_field_value.py +5 -3
- regscale/models/regscale_models/inheritance.py +44 -0
- regscale/models/regscale_models/issue.py +301 -102
- regscale/models/regscale_models/milestone.py +33 -14
- regscale/models/regscale_models/organization.py +3 -0
- regscale/models/regscale_models/regscale_model.py +310 -73
- regscale/models/regscale_models/security_plan.py +4 -2
- regscale/models/regscale_models/vulnerability.py +3 -3
- regscale/regscale.py +25 -4
- regscale/templates/__init__.py +0 -0
- regscale/utils/threading/threadhandler.py +20 -15
- regscale/validation/record.py +23 -1
- {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/METADATA +17 -33
- {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/RECORD +310 -111
- tests/core/__init__.py +0 -0
- tests/core/utils/__init__.py +0 -0
- tests/core/utils/test_async_graphql_client.py +472 -0
- tests/fixtures/test_fixture.py +13 -8
- tests/regscale/core/test_login.py +171 -4
- tests/regscale/integrations/commercial/__init__.py +0 -0
- 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_analytics_collector.py +260 -0
- tests/regscale/integrations/commercial/aws/test_aws_applications_collector.py +242 -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_developer_tools_collector.py +203 -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_machine_learning_collector.py +237 -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_control_compliance_analyzer.py +375 -0
- tests/regscale/integrations/commercial/aws/test_datetime_parsing.py +223 -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/conftest.py +28 -0
- tests/regscale/integrations/commercial/microsoft_defender/__init__.py +1 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender.py +1517 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_api.py +1748 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_constants.py +327 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_scanner.py +487 -0
- tests/regscale/integrations/commercial/test_aws.py +3742 -0
- tests/regscale/integrations/commercial/test_burp.py +48 -0
- tests/regscale/integrations/commercial/test_crowdstrike.py +49 -0
- tests/regscale/integrations/commercial/test_dependabot.py +341 -0
- tests/regscale/integrations/commercial/test_gcp.py +1543 -0
- tests/regscale/integrations/commercial/test_gitlab.py +549 -0
- tests/regscale/integrations/commercial/test_ip_mac_address_length.py +84 -0
- tests/regscale/integrations/commercial/test_jira.py +2204 -0
- tests/regscale/integrations/commercial/test_npm_audit.py +42 -0
- tests/regscale/integrations/commercial/test_okta.py +1228 -0
- tests/regscale/integrations/commercial/test_sarif_converter.py +251 -0
- tests/regscale/integrations/commercial/test_sicura.py +349 -0
- tests/regscale/integrations/commercial/test_snow.py +423 -0
- tests/regscale/integrations/commercial/test_sonarcloud.py +394 -0
- tests/regscale/integrations/commercial/test_sqlserver.py +186 -0
- tests/regscale/integrations/commercial/test_stig.py +33 -0
- tests/regscale/integrations/commercial/test_stig_mapper.py +153 -0
- tests/regscale/integrations/commercial/test_stigv2.py +406 -0
- tests/regscale/integrations/commercial/test_wiz.py +1365 -0
- tests/regscale/integrations/commercial/test_wiz_inventory.py +256 -0
- tests/regscale/integrations/commercial/wizv2/__init__.py +339 -0
- tests/regscale/integrations/commercial/wizv2/compliance/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/compliance/test_helpers.py +903 -0
- tests/regscale/integrations/commercial/wizv2/core/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/core/test_auth.py +701 -0
- tests/regscale/integrations/commercial/wizv2/core/test_client.py +1037 -0
- tests/regscale/integrations/commercial/wizv2/core/test_file_operations.py +989 -0
- tests/regscale/integrations/commercial/wizv2/fetchers/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/fetchers/test_policy_assessment.py +805 -0
- tests/regscale/integrations/commercial/wizv2/parsers/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/parsers/test_main.py +1153 -0
- tests/regscale/integrations/commercial/wizv2/processors/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/processors/test_finding.py +671 -0
- tests/regscale/integrations/commercial/wizv2/test_WizDataMixin.py +537 -0
- tests/regscale/integrations/commercial/wizv2/test_click_comprehensive.py +851 -0
- tests/regscale/integrations/commercial/wizv2/test_compliance_report_comprehensive.py +910 -0
- tests/regscale/integrations/commercial/wizv2/test_compliance_report_normalization.py +138 -0
- tests/regscale/integrations/commercial/wizv2/test_file_cleanup.py +283 -0
- tests/regscale/integrations/commercial/wizv2/test_file_operations.py +260 -0
- tests/regscale/integrations/commercial/wizv2/test_issue.py +343 -0
- tests/regscale/integrations/commercial/wizv2/test_issue_comprehensive.py +1203 -0
- tests/regscale/integrations/commercial/wizv2/test_reports.py +497 -0
- tests/regscale/integrations/commercial/wizv2/test_sbom.py +643 -0
- tests/regscale/integrations/commercial/wizv2/test_scanner_comprehensive.py +805 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_click_client_id.py +165 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_report.py +1394 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_unit.py +341 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_control_normalization.py +138 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_findings_comprehensive.py +364 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_inventory_comprehensive.py +644 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_status_mapping.py +149 -0
- tests/regscale/integrations/commercial/wizv2/test_wizv2.py +1218 -0
- tests/regscale/integrations/commercial/wizv2/test_wizv2_utils.py +519 -0
- tests/regscale/integrations/commercial/wizv2/utils/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/utils/test_main.py +1523 -0
- tests/regscale/integrations/public/__init__.py +0 -0
- tests/regscale/integrations/public/fedramp/__init__.py +1 -0
- tests/regscale/integrations/public/fedramp/test_gen_asset_list.py +150 -0
- tests/regscale/integrations/public/fedramp/test_poam_export_v5.py +1293 -0
- tests/regscale/integrations/public/test_alienvault.py +220 -0
- tests/regscale/integrations/public/test_cci.py +1053 -0
- tests/regscale/integrations/public/test_cisa.py +1021 -0
- tests/regscale/integrations/public/test_emass.py +518 -0
- tests/regscale/integrations/public/test_fedramp.py +1152 -0
- tests/regscale/integrations/public/test_fedramp_cis_crm.py +3661 -0
- tests/regscale/integrations/public/test_file_uploads.py +506 -0
- tests/regscale/integrations/public/test_oscal.py +453 -0
- tests/regscale/integrations/test_compliance_status_mapping.py +406 -0
- tests/regscale/integrations/test_control_matcher.py +1421 -0
- tests/regscale/integrations/test_control_matching.py +155 -0
- tests/regscale/integrations/test_milestone_manager.py +408 -0
- tests/regscale/models/test_control_implementation.py +118 -3
- tests/regscale/models/test_form_field_value_integration.py +304 -0
- tests/regscale/models/test_issue.py +378 -1
- tests/regscale/models/test_module_integration.py +582 -0
- tests/regscale/models/test_tenable_integrations.py +811 -105
- regscale/integrations/commercial/wizv2/policy_compliance.py +0 -3057
- regscale/integrations/public/fedramp/mappings/fedramp_r4_parts.json +0 -7388
- regscale/integrations/public/fedramp/mappings/fedramp_r5_parts.json +0 -9605
- regscale/integrations/public/fedramp/parts_mapper.py +0 -107
- /regscale/integrations/commercial/{amazon → sarif}/__init__.py +0 -0
- /regscale/integrations/commercial/wizv2/{wiz_auth.py → core/auth.py} +0 -0
- {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/LICENSE +0 -0
- {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/WHEEL +0 -0
- {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.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}"
|