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,476 @@
|
|
|
1
|
+
"""AWS Inspector v2 resource collection."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Any, Dict, List, Optional
|
|
5
|
+
|
|
6
|
+
from botocore.exceptions import ClientError
|
|
7
|
+
|
|
8
|
+
from regscale.integrations.commercial.aws.inventory.base import BaseCollector
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger("regscale")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class InspectorCollector(BaseCollector):
|
|
14
|
+
"""Collector for AWS Inspector v2 resources."""
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
session: Any,
|
|
19
|
+
region: str,
|
|
20
|
+
account_id: Optional[str] = None,
|
|
21
|
+
tags: Optional[Dict[str, str]] = None,
|
|
22
|
+
collect_findings: bool = True,
|
|
23
|
+
):
|
|
24
|
+
"""
|
|
25
|
+
Initialize Inspector collector.
|
|
26
|
+
|
|
27
|
+
:param session: AWS session to use for API calls
|
|
28
|
+
:param str region: AWS region to collect from
|
|
29
|
+
:param str account_id: Optional AWS account ID to filter resources
|
|
30
|
+
:param Dict[str, str] tags: Optional tags to filter resources (all must match)
|
|
31
|
+
:param bool collect_findings: Whether to collect Inspector findings. Default True.
|
|
32
|
+
"""
|
|
33
|
+
super().__init__(session, region)
|
|
34
|
+
self.account_id = account_id
|
|
35
|
+
self.tags = tags or {}
|
|
36
|
+
self.collect_findings = collect_findings
|
|
37
|
+
|
|
38
|
+
def collect(self) -> Dict[str, Any]:
|
|
39
|
+
"""
|
|
40
|
+
Collect AWS Inspector v2 resources.
|
|
41
|
+
|
|
42
|
+
:return: Dictionary containing Inspector findings and coverage information
|
|
43
|
+
:rtype: Dict[str, Any]
|
|
44
|
+
"""
|
|
45
|
+
result = {
|
|
46
|
+
"Findings": [],
|
|
47
|
+
"Coverage": [],
|
|
48
|
+
"AccountStatus": {},
|
|
49
|
+
"Members": [],
|
|
50
|
+
"CoverageStatistics": {},
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
client = self._get_client("inspector2")
|
|
55
|
+
|
|
56
|
+
# Get account status
|
|
57
|
+
account_status = self._get_account_status(client)
|
|
58
|
+
result["AccountStatus"] = account_status
|
|
59
|
+
|
|
60
|
+
# Get coverage information
|
|
61
|
+
coverage = self._list_coverage(client)
|
|
62
|
+
result["Coverage"] = coverage
|
|
63
|
+
|
|
64
|
+
# Get coverage statistics
|
|
65
|
+
coverage_stats = self._list_coverage_statistics(client)
|
|
66
|
+
result["CoverageStatistics"] = coverage_stats
|
|
67
|
+
|
|
68
|
+
# Get findings only if requested
|
|
69
|
+
if self.collect_findings:
|
|
70
|
+
findings = self._list_findings(client)
|
|
71
|
+
result["Findings"] = findings
|
|
72
|
+
else:
|
|
73
|
+
findings = []
|
|
74
|
+
logger.debug("Skipping Inspector findings collection (collect_findings=False)")
|
|
75
|
+
|
|
76
|
+
# Get member accounts
|
|
77
|
+
members = self._list_members(client)
|
|
78
|
+
result["Members"] = members
|
|
79
|
+
|
|
80
|
+
if self.collect_findings:
|
|
81
|
+
logger.info(
|
|
82
|
+
f"Collected {len(findings)} Inspector finding(s), "
|
|
83
|
+
f"{len(coverage)} covered resource(s) from {self.region}"
|
|
84
|
+
)
|
|
85
|
+
else:
|
|
86
|
+
logger.info(f"Collected {len(coverage)} covered resource(s) from {self.region}")
|
|
87
|
+
|
|
88
|
+
except ClientError as e:
|
|
89
|
+
self._handle_error(e, "AWS Inspector resources")
|
|
90
|
+
except Exception as e:
|
|
91
|
+
logger.error(f"Unexpected error collecting AWS Inspector resources: {e}", exc_info=True)
|
|
92
|
+
|
|
93
|
+
return result
|
|
94
|
+
|
|
95
|
+
def _get_account_status(self, client: Any) -> Dict[str, Any]:
|
|
96
|
+
"""
|
|
97
|
+
Get account status for Inspector.
|
|
98
|
+
|
|
99
|
+
:param client: Inspector client
|
|
100
|
+
:return: Account status information
|
|
101
|
+
:rtype: Dict[str, Any]
|
|
102
|
+
"""
|
|
103
|
+
try:
|
|
104
|
+
if self.account_id:
|
|
105
|
+
response = client.batch_get_account_status(accountIds=[self.account_id])
|
|
106
|
+
else:
|
|
107
|
+
# If no account ID specified, get current account status
|
|
108
|
+
response = client.batch_get_account_status()
|
|
109
|
+
|
|
110
|
+
accounts = response.get("accounts", [])
|
|
111
|
+
if accounts:
|
|
112
|
+
status = accounts[0]
|
|
113
|
+
status["Region"] = self.region
|
|
114
|
+
return status
|
|
115
|
+
|
|
116
|
+
return {}
|
|
117
|
+
except ClientError as e:
|
|
118
|
+
if e.response["Error"]["Code"] == "AccessDeniedException":
|
|
119
|
+
logger.warning(f"Access denied to get Inspector account status in {self.region}")
|
|
120
|
+
else:
|
|
121
|
+
logger.error(f"Error getting Inspector account status: {e}")
|
|
122
|
+
return {}
|
|
123
|
+
|
|
124
|
+
def _list_coverage(self, client: Any, max_results: int = 100) -> List[Dict[str, Any]]:
|
|
125
|
+
"""
|
|
126
|
+
List resources covered by Inspector with tag filtering.
|
|
127
|
+
|
|
128
|
+
:param client: Inspector client
|
|
129
|
+
:param int max_results: Maximum number of results to retrieve
|
|
130
|
+
:return: List of covered resources
|
|
131
|
+
:rtype: List[Dict[str, Any]]
|
|
132
|
+
"""
|
|
133
|
+
coverage = []
|
|
134
|
+
next_token = None
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
while True:
|
|
138
|
+
params = self._build_coverage_params(max_results, next_token)
|
|
139
|
+
response = client.list_coverage(**params)
|
|
140
|
+
covered_resources = response.get("coveredResources", [])
|
|
141
|
+
|
|
142
|
+
processed_resources = self._process_coverage_resources(covered_resources)
|
|
143
|
+
coverage.extend(processed_resources)
|
|
144
|
+
|
|
145
|
+
next_token = response.get("nextToken")
|
|
146
|
+
if not next_token:
|
|
147
|
+
break
|
|
148
|
+
|
|
149
|
+
except ClientError as e:
|
|
150
|
+
self._handle_client_error(e, "list Inspector coverage")
|
|
151
|
+
|
|
152
|
+
return coverage
|
|
153
|
+
|
|
154
|
+
def _build_coverage_params(self, max_results: int, next_token: Optional[str]) -> Dict[str, Any]:
|
|
155
|
+
"""
|
|
156
|
+
Build parameters for list_coverage API call.
|
|
157
|
+
|
|
158
|
+
:param int max_results: Maximum number of results to retrieve
|
|
159
|
+
:param next_token: Pagination token
|
|
160
|
+
:return: API parameters dictionary
|
|
161
|
+
:rtype: Dict[str, Any]
|
|
162
|
+
"""
|
|
163
|
+
params = {"maxResults": max_results}
|
|
164
|
+
|
|
165
|
+
if next_token:
|
|
166
|
+
params["nextToken"] = next_token
|
|
167
|
+
|
|
168
|
+
if self.account_id:
|
|
169
|
+
params["filterCriteria"] = {"accountId": [{"comparison": "EQUALS", "value": self.account_id}]}
|
|
170
|
+
|
|
171
|
+
return params
|
|
172
|
+
|
|
173
|
+
def _process_coverage_resources(self, resources: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
174
|
+
"""
|
|
175
|
+
Process and filter coverage resources by tags.
|
|
176
|
+
|
|
177
|
+
:param resources: List of coverage resources
|
|
178
|
+
:return: Filtered list of resources
|
|
179
|
+
:rtype: List[Dict[str, Any]]
|
|
180
|
+
"""
|
|
181
|
+
processed = []
|
|
182
|
+
|
|
183
|
+
for resource in resources:
|
|
184
|
+
resource["Region"] = self.region
|
|
185
|
+
|
|
186
|
+
if self._should_include_coverage_resource(resource):
|
|
187
|
+
processed.append(resource)
|
|
188
|
+
|
|
189
|
+
return processed
|
|
190
|
+
|
|
191
|
+
def _should_include_coverage_resource(self, resource: Dict[str, Any]) -> bool:
|
|
192
|
+
"""
|
|
193
|
+
Check if coverage resource should be included based on tag filter.
|
|
194
|
+
|
|
195
|
+
:param resource: Coverage resource to check
|
|
196
|
+
:return: True if resource should be included
|
|
197
|
+
:rtype: bool
|
|
198
|
+
"""
|
|
199
|
+
if not self.tags:
|
|
200
|
+
return True
|
|
201
|
+
|
|
202
|
+
resource_metadata = resource.get("resourceMetadata", {})
|
|
203
|
+
resource_tags = resource_metadata.get("tags", {})
|
|
204
|
+
|
|
205
|
+
if self._matches_tags(resource_tags):
|
|
206
|
+
return True
|
|
207
|
+
|
|
208
|
+
logger.debug(
|
|
209
|
+
"Filtering out Inspector coverage resource %s - tags do not match filter",
|
|
210
|
+
resource.get("resourceId", "unknown"),
|
|
211
|
+
)
|
|
212
|
+
return False
|
|
213
|
+
|
|
214
|
+
def _list_coverage_statistics(self, client: Any) -> Dict[str, Any]:
|
|
215
|
+
"""
|
|
216
|
+
Get coverage statistics.
|
|
217
|
+
|
|
218
|
+
:param client: Inspector client
|
|
219
|
+
:return: Coverage statistics
|
|
220
|
+
:rtype: Dict[str, Any]
|
|
221
|
+
"""
|
|
222
|
+
try:
|
|
223
|
+
params = {}
|
|
224
|
+
|
|
225
|
+
if self.account_id:
|
|
226
|
+
params["filterCriteria"] = {"accountId": [{"comparison": "EQUALS", "value": self.account_id}]}
|
|
227
|
+
|
|
228
|
+
response = client.list_coverage_statistics(**params)
|
|
229
|
+
counts_by_group = response.get("countsByGroup", [])
|
|
230
|
+
|
|
231
|
+
stats = {"Region": self.region, "CountsByGroup": counts_by_group}
|
|
232
|
+
|
|
233
|
+
return stats
|
|
234
|
+
except ClientError as e:
|
|
235
|
+
if e.response["Error"]["Code"] == "AccessDeniedException":
|
|
236
|
+
logger.debug(f"Access denied to list Inspector coverage statistics in {self.region}")
|
|
237
|
+
else:
|
|
238
|
+
logger.error(f"Error listing Inspector coverage statistics: {e}")
|
|
239
|
+
return {}
|
|
240
|
+
|
|
241
|
+
def _list_findings(self, client: Any, max_results: int = 100) -> List[Dict[str, Any]]:
|
|
242
|
+
"""
|
|
243
|
+
List Inspector findings with pagination support and tag filtering.
|
|
244
|
+
|
|
245
|
+
:param client: Inspector client
|
|
246
|
+
:param int max_results: Maximum number of results per page
|
|
247
|
+
:return: List of findings
|
|
248
|
+
:rtype: List[Dict[str, Any]]
|
|
249
|
+
"""
|
|
250
|
+
findings = []
|
|
251
|
+
next_token = None
|
|
252
|
+
|
|
253
|
+
try:
|
|
254
|
+
while True:
|
|
255
|
+
params = self._build_findings_params(max_results, next_token)
|
|
256
|
+
response = client.list_findings(**params)
|
|
257
|
+
finding_list = response.get("findings", [])
|
|
258
|
+
|
|
259
|
+
processed_findings = self._process_findings(finding_list)
|
|
260
|
+
findings.extend(processed_findings)
|
|
261
|
+
|
|
262
|
+
next_token = response.get("nextToken")
|
|
263
|
+
if not next_token:
|
|
264
|
+
break
|
|
265
|
+
|
|
266
|
+
except ClientError as e:
|
|
267
|
+
self._handle_client_error(e, "list Inspector findings")
|
|
268
|
+
|
|
269
|
+
return findings
|
|
270
|
+
|
|
271
|
+
def _build_findings_params(self, max_results: int, next_token: Optional[str]) -> Dict[str, Any]:
|
|
272
|
+
"""
|
|
273
|
+
Build parameters for list_findings API call.
|
|
274
|
+
|
|
275
|
+
:param int max_results: Maximum number of results per page
|
|
276
|
+
:param next_token: Pagination token
|
|
277
|
+
:return: API parameters dictionary
|
|
278
|
+
:rtype: Dict[str, Any]
|
|
279
|
+
"""
|
|
280
|
+
params = {"maxResults": max_results}
|
|
281
|
+
|
|
282
|
+
if next_token:
|
|
283
|
+
params["nextToken"] = next_token
|
|
284
|
+
|
|
285
|
+
if self.account_id:
|
|
286
|
+
params["filterCriteria"] = {"awsAccountId": [{"comparison": "EQUALS", "value": self.account_id}]}
|
|
287
|
+
|
|
288
|
+
return params
|
|
289
|
+
|
|
290
|
+
def _process_findings(self, finding_list: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
291
|
+
"""
|
|
292
|
+
Process and filter findings by tags.
|
|
293
|
+
|
|
294
|
+
:param finding_list: List of findings to process
|
|
295
|
+
:return: Filtered list of findings
|
|
296
|
+
:rtype: List[Dict[str, Any]]
|
|
297
|
+
"""
|
|
298
|
+
processed = []
|
|
299
|
+
|
|
300
|
+
for finding in finding_list:
|
|
301
|
+
finding["Region"] = self.region
|
|
302
|
+
|
|
303
|
+
if self._should_include_finding(finding):
|
|
304
|
+
processed.append(finding)
|
|
305
|
+
|
|
306
|
+
return processed
|
|
307
|
+
|
|
308
|
+
def _should_include_finding(self, finding: Dict[str, Any]) -> bool:
|
|
309
|
+
"""
|
|
310
|
+
Check if finding should be included based on tag filter.
|
|
311
|
+
|
|
312
|
+
:param finding: Finding to check
|
|
313
|
+
:return: True if finding should be included
|
|
314
|
+
:rtype: bool
|
|
315
|
+
"""
|
|
316
|
+
if not self.tags:
|
|
317
|
+
return True
|
|
318
|
+
|
|
319
|
+
if self._finding_matches_tag_filter(finding):
|
|
320
|
+
return True
|
|
321
|
+
|
|
322
|
+
logger.debug(
|
|
323
|
+
"Filtering out Inspector finding %s - tags do not match filter", finding.get("findingArn", "unknown")
|
|
324
|
+
)
|
|
325
|
+
return False
|
|
326
|
+
|
|
327
|
+
def _finding_matches_tag_filter(self, finding: Dict[str, Any]) -> bool:
|
|
328
|
+
"""
|
|
329
|
+
Check if any resource in the finding matches the tag filter.
|
|
330
|
+
|
|
331
|
+
:param finding: Finding to check
|
|
332
|
+
:return: True if any resource matches the tag filter
|
|
333
|
+
:rtype: bool
|
|
334
|
+
"""
|
|
335
|
+
resources = finding.get("resources", [])
|
|
336
|
+
|
|
337
|
+
for resource in resources:
|
|
338
|
+
resource_tags = resource.get("tags", {})
|
|
339
|
+
if self._matches_tags(resource_tags):
|
|
340
|
+
return True
|
|
341
|
+
|
|
342
|
+
return False
|
|
343
|
+
|
|
344
|
+
def _list_members(self, client: Any) -> List[Dict[str, Any]]:
|
|
345
|
+
"""
|
|
346
|
+
List member accounts for Inspector organization.
|
|
347
|
+
|
|
348
|
+
:param client: Inspector client
|
|
349
|
+
:return: List of member accounts
|
|
350
|
+
:rtype: List[Dict[str, Any]]
|
|
351
|
+
"""
|
|
352
|
+
members = []
|
|
353
|
+
next_token = None
|
|
354
|
+
|
|
355
|
+
try:
|
|
356
|
+
while True:
|
|
357
|
+
params = {}
|
|
358
|
+
|
|
359
|
+
if next_token:
|
|
360
|
+
params["nextToken"] = next_token
|
|
361
|
+
|
|
362
|
+
response = client.list_members(**params)
|
|
363
|
+
member_list = response.get("members", [])
|
|
364
|
+
|
|
365
|
+
for member in member_list:
|
|
366
|
+
# Filter by account ID if specified
|
|
367
|
+
if self.account_id and member.get("accountId") != self.account_id:
|
|
368
|
+
continue
|
|
369
|
+
|
|
370
|
+
member["Region"] = self.region
|
|
371
|
+
|
|
372
|
+
members.extend(member_list)
|
|
373
|
+
|
|
374
|
+
next_token = response.get("nextToken")
|
|
375
|
+
if not next_token:
|
|
376
|
+
break
|
|
377
|
+
|
|
378
|
+
except ClientError as e:
|
|
379
|
+
if e.response["Error"]["Code"] == "AccessDeniedException":
|
|
380
|
+
logger.debug(f"Access denied to list Inspector members in {self.region}")
|
|
381
|
+
else:
|
|
382
|
+
logger.error(f"Error listing Inspector members: {e}")
|
|
383
|
+
|
|
384
|
+
return members
|
|
385
|
+
|
|
386
|
+
def _convert_tags_to_dict(self, tags: Any) -> Dict[str, str]:
|
|
387
|
+
"""
|
|
388
|
+
Convert tags from various formats to a dictionary.
|
|
389
|
+
|
|
390
|
+
:param tags: Tags in list format [{"key": "k", "value": "v"}] or dict format
|
|
391
|
+
:return: Dictionary of tag key-value pairs
|
|
392
|
+
:rtype: Dict[str, str]
|
|
393
|
+
"""
|
|
394
|
+
if not tags:
|
|
395
|
+
return {}
|
|
396
|
+
|
|
397
|
+
if isinstance(tags, dict):
|
|
398
|
+
return tags
|
|
399
|
+
|
|
400
|
+
if isinstance(tags, list):
|
|
401
|
+
return self._convert_tag_list_to_dict(tags)
|
|
402
|
+
|
|
403
|
+
return {}
|
|
404
|
+
|
|
405
|
+
def _convert_tag_list_to_dict(self, tags: List[Any]) -> Dict[str, str]:
|
|
406
|
+
"""
|
|
407
|
+
Convert a list of tag dictionaries to a single dictionary.
|
|
408
|
+
|
|
409
|
+
:param tags: List of tag dictionaries
|
|
410
|
+
:return: Dictionary of tag key-value pairs
|
|
411
|
+
:rtype: Dict[str, str]
|
|
412
|
+
"""
|
|
413
|
+
tag_dict = {}
|
|
414
|
+
|
|
415
|
+
for tag in tags:
|
|
416
|
+
if isinstance(tag, dict):
|
|
417
|
+
key_value = self._extract_tag_key_value(tag)
|
|
418
|
+
if key_value:
|
|
419
|
+
tag_dict[key_value[0]] = key_value[1]
|
|
420
|
+
|
|
421
|
+
return tag_dict
|
|
422
|
+
|
|
423
|
+
def _extract_tag_key_value(self, tag: Dict[str, Any]) -> Optional[tuple]:
|
|
424
|
+
"""
|
|
425
|
+
Extract key and value from a tag dictionary.
|
|
426
|
+
|
|
427
|
+
Handles both lowercase and uppercase formats:
|
|
428
|
+
- {"key": "k", "value": "v"}
|
|
429
|
+
- {"Key": "k", "Value": "v"}
|
|
430
|
+
|
|
431
|
+
:param tag: Tag dictionary
|
|
432
|
+
:return: Tuple of (key, value) or None if key not found
|
|
433
|
+
:rtype: Optional[tuple]
|
|
434
|
+
"""
|
|
435
|
+
key = tag.get("key") or tag.get("Key")
|
|
436
|
+
if key is None:
|
|
437
|
+
return None
|
|
438
|
+
|
|
439
|
+
value = tag.get("value") or tag.get("Value")
|
|
440
|
+
return (key, value if value is not None else "")
|
|
441
|
+
|
|
442
|
+
def _handle_client_error(self, error: ClientError, operation: str) -> None:
|
|
443
|
+
"""
|
|
444
|
+
Handle ClientError exceptions consistently.
|
|
445
|
+
|
|
446
|
+
:param error: The ClientError exception
|
|
447
|
+
:param str operation: Description of the operation that failed
|
|
448
|
+
"""
|
|
449
|
+
error_code = error.response["Error"]["Code"]
|
|
450
|
+
if error_code == "AccessDeniedException":
|
|
451
|
+
logger.warning("Access denied to %s in %s", operation, self.region)
|
|
452
|
+
else:
|
|
453
|
+
logger.error("Error %s: %s", operation, error)
|
|
454
|
+
|
|
455
|
+
def _matches_tags(self, resource_tags: Any) -> bool:
|
|
456
|
+
"""
|
|
457
|
+
Check if resource tags match all filter tags.
|
|
458
|
+
|
|
459
|
+
:param resource_tags: Tags from the resource (can be dict or list)
|
|
460
|
+
:return: True if all filter tags match, False otherwise
|
|
461
|
+
:rtype: bool
|
|
462
|
+
"""
|
|
463
|
+
if not self.tags:
|
|
464
|
+
# No tag filter, so all resources match
|
|
465
|
+
return True
|
|
466
|
+
|
|
467
|
+
resource_tag_dict = self._convert_tags_to_dict(resource_tags)
|
|
468
|
+
|
|
469
|
+
# All filter tags must match
|
|
470
|
+
for filter_key, filter_value in self.tags.items():
|
|
471
|
+
if filter_key not in resource_tag_dict:
|
|
472
|
+
return False
|
|
473
|
+
if resource_tag_dict[filter_key] != filter_value:
|
|
474
|
+
return False
|
|
475
|
+
|
|
476
|
+
return True
|