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,447 @@
|
|
|
1
|
+
"""AWS KMS 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 KMSCollector(BaseCollector):
|
|
14
|
+
"""Collector for AWS KMS resources."""
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self, session: Any, region: str, account_id: Optional[str] = None, tags: Optional[Dict[str, str]] = None
|
|
18
|
+
):
|
|
19
|
+
"""
|
|
20
|
+
Initialize KMS collector.
|
|
21
|
+
|
|
22
|
+
:param session: AWS session to use for API calls
|
|
23
|
+
:param str region: AWS region to collect from
|
|
24
|
+
:param str account_id: Optional AWS account ID to filter resources
|
|
25
|
+
:param dict tags: Optional tags to filter resources (key-value pairs)
|
|
26
|
+
"""
|
|
27
|
+
super().__init__(session, region)
|
|
28
|
+
self.account_id = account_id
|
|
29
|
+
self.tags = tags or {}
|
|
30
|
+
|
|
31
|
+
def collect(self) -> Dict[str, Any]:
|
|
32
|
+
"""
|
|
33
|
+
Collect AWS KMS resources.
|
|
34
|
+
|
|
35
|
+
:return: Dictionary containing KMS key information
|
|
36
|
+
:rtype: Dict[str, Any]
|
|
37
|
+
"""
|
|
38
|
+
result = {"Keys": [], "Aliases": []}
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
client = self._get_client("kms")
|
|
42
|
+
|
|
43
|
+
# Get all keys
|
|
44
|
+
keys = self._list_keys(client)
|
|
45
|
+
result["Keys"] = keys
|
|
46
|
+
|
|
47
|
+
# Get all aliases
|
|
48
|
+
aliases = self._list_aliases(client)
|
|
49
|
+
result["Aliases"] = aliases
|
|
50
|
+
|
|
51
|
+
logger.info(f"Collected {len(keys)} KMS key(s), {len(aliases)} alias(es) from {self.region}")
|
|
52
|
+
|
|
53
|
+
except ClientError as e:
|
|
54
|
+
self._handle_error(e, "KMS keys")
|
|
55
|
+
except Exception as e:
|
|
56
|
+
logger.error(f"Unexpected error collecting KMS resources: {e}", exc_info=True)
|
|
57
|
+
|
|
58
|
+
return result
|
|
59
|
+
|
|
60
|
+
def _list_keys(self, client: Any) -> List[Dict[str, Any]]:
|
|
61
|
+
"""
|
|
62
|
+
List KMS keys with enhanced details.
|
|
63
|
+
|
|
64
|
+
:param client: KMS client
|
|
65
|
+
:return: List of key information
|
|
66
|
+
:rtype: List[Dict[str, Any]]
|
|
67
|
+
"""
|
|
68
|
+
keys = []
|
|
69
|
+
try:
|
|
70
|
+
paginator = client.get_paginator("list_keys")
|
|
71
|
+
|
|
72
|
+
for page in paginator.paginate():
|
|
73
|
+
for key in page.get("Keys", []):
|
|
74
|
+
key_id = key["KeyId"]
|
|
75
|
+
processed_key = self._process_key(client, key_id)
|
|
76
|
+
if processed_key:
|
|
77
|
+
keys.append(processed_key)
|
|
78
|
+
|
|
79
|
+
except ClientError as e:
|
|
80
|
+
self._handle_list_keys_error(e)
|
|
81
|
+
|
|
82
|
+
return keys
|
|
83
|
+
|
|
84
|
+
def _process_key(self, client: Any, key_id: str) -> Optional[Dict[str, Any]]:
|
|
85
|
+
"""
|
|
86
|
+
Process a single KMS key and return its details if it passes filters.
|
|
87
|
+
|
|
88
|
+
:param client: KMS client
|
|
89
|
+
:param str key_id: Key ID to process
|
|
90
|
+
:return: Key information if it passes filters, None otherwise
|
|
91
|
+
:rtype: Optional[Dict[str, Any]]
|
|
92
|
+
"""
|
|
93
|
+
try:
|
|
94
|
+
key_info = self._describe_key(client, key_id)
|
|
95
|
+
if not key_info:
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
if not self._passes_account_filter(key_info):
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
self._enrich_key_info(client, key_id, key_info)
|
|
102
|
+
|
|
103
|
+
if not self._passes_tag_filter(client, key_id):
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
return key_info
|
|
107
|
+
|
|
108
|
+
except ClientError as e:
|
|
109
|
+
self._handle_key_processing_error(e, key_id)
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
def _passes_account_filter(self, key_info: Dict[str, Any]) -> bool:
|
|
113
|
+
"""
|
|
114
|
+
Check if key passes account ID filter.
|
|
115
|
+
|
|
116
|
+
:param dict key_info: Key information dictionary
|
|
117
|
+
:return: True if key passes account filter
|
|
118
|
+
:rtype: bool
|
|
119
|
+
"""
|
|
120
|
+
if not self.account_id:
|
|
121
|
+
return True
|
|
122
|
+
return self._matches_account_id(key_info.get("Arn", ""))
|
|
123
|
+
|
|
124
|
+
def _enrich_key_info(self, client: Any, key_id: str, key_info: Dict[str, Any]) -> None:
|
|
125
|
+
"""
|
|
126
|
+
Enrich key information with additional details.
|
|
127
|
+
|
|
128
|
+
:param client: KMS client
|
|
129
|
+
:param str key_id: Key ID
|
|
130
|
+
:param dict key_info: Key information dictionary to enrich
|
|
131
|
+
"""
|
|
132
|
+
key_info["Region"] = self.region
|
|
133
|
+
key_info["RotationEnabled"] = self._get_key_rotation_status(client, key_id)
|
|
134
|
+
key_info["Policy"] = self._get_key_policy(client, key_id)
|
|
135
|
+
|
|
136
|
+
grants = self._list_grants(client, key_id)
|
|
137
|
+
key_info["GrantCount"] = len(grants)
|
|
138
|
+
|
|
139
|
+
tags = self._list_resource_tags(client, key_id)
|
|
140
|
+
key_info["Tags"] = tags
|
|
141
|
+
|
|
142
|
+
def _passes_tag_filter(self, client: Any, key_id: str) -> bool:
|
|
143
|
+
"""
|
|
144
|
+
Check if key passes tag filter.
|
|
145
|
+
|
|
146
|
+
:param client: KMS client
|
|
147
|
+
:param str key_id: Key ID
|
|
148
|
+
:return: True if key passes tag filter
|
|
149
|
+
:rtype: bool
|
|
150
|
+
"""
|
|
151
|
+
if not self.tags:
|
|
152
|
+
return True
|
|
153
|
+
|
|
154
|
+
key_tags = self._get_key_tags(client, key_id)
|
|
155
|
+
if not self._matches_tags(key_tags):
|
|
156
|
+
logger.debug(f"Skipping key {key_id} - does not match tag filters")
|
|
157
|
+
return False
|
|
158
|
+
return True
|
|
159
|
+
|
|
160
|
+
def _handle_list_keys_error(self, error: ClientError) -> None:
|
|
161
|
+
"""
|
|
162
|
+
Handle errors from list_keys operation.
|
|
163
|
+
|
|
164
|
+
:param error: Client error exception
|
|
165
|
+
"""
|
|
166
|
+
if error.response["Error"]["Code"] == "AccessDeniedException":
|
|
167
|
+
logger.warning(f"Access denied to list KMS keys in {self.region}")
|
|
168
|
+
else:
|
|
169
|
+
logger.error(f"Error listing KMS keys: {error}")
|
|
170
|
+
|
|
171
|
+
def _handle_key_processing_error(self, error: ClientError, key_id: str) -> None:
|
|
172
|
+
"""
|
|
173
|
+
Handle errors from processing individual keys.
|
|
174
|
+
|
|
175
|
+
:param error: Client error exception
|
|
176
|
+
:param str key_id: Key ID being processed
|
|
177
|
+
"""
|
|
178
|
+
if error.response["Error"]["Code"] not in ["NotFoundException", "AccessDeniedException"]:
|
|
179
|
+
logger.error(f"Error getting details for key {key_id}: {error}")
|
|
180
|
+
|
|
181
|
+
def _describe_key(self, client: Any, key_id: str) -> Optional[Dict[str, Any]]:
|
|
182
|
+
"""
|
|
183
|
+
Get key metadata.
|
|
184
|
+
|
|
185
|
+
:param client: KMS client
|
|
186
|
+
:param str key_id: Key ID
|
|
187
|
+
:return: Key metadata
|
|
188
|
+
:rtype: Optional[Dict[str, Any]]
|
|
189
|
+
"""
|
|
190
|
+
try:
|
|
191
|
+
response = client.describe_key(KeyId=key_id)
|
|
192
|
+
key_metadata = response["KeyMetadata"]
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
"KeyId": key_metadata.get("KeyId"),
|
|
196
|
+
"Arn": key_metadata.get("Arn"),
|
|
197
|
+
"Description": key_metadata.get("Description"),
|
|
198
|
+
"Enabled": key_metadata.get("Enabled"),
|
|
199
|
+
"KeyState": key_metadata.get("KeyState"),
|
|
200
|
+
"CreationDate": str(key_metadata.get("CreationDate")),
|
|
201
|
+
"DeletionDate": str(key_metadata.get("DeletionDate")) if key_metadata.get("DeletionDate") else None,
|
|
202
|
+
"Origin": key_metadata.get("Origin"),
|
|
203
|
+
"KeyManager": key_metadata.get("KeyManager"),
|
|
204
|
+
"KeySpec": key_metadata.get("KeySpec"),
|
|
205
|
+
"KeyUsage": key_metadata.get("KeyUsage"),
|
|
206
|
+
"MultiRegion": key_metadata.get("MultiRegion", False),
|
|
207
|
+
"MultiRegionConfiguration": key_metadata.get("MultiRegionConfiguration"),
|
|
208
|
+
}
|
|
209
|
+
except ClientError as e:
|
|
210
|
+
if e.response["Error"]["Code"] not in ["NotFoundException", "AccessDeniedException"]:
|
|
211
|
+
logger.error(f"Error describing key {key_id}: {e}")
|
|
212
|
+
return None
|
|
213
|
+
|
|
214
|
+
def _get_key_rotation_status(self, client: Any, key_id: str) -> bool:
|
|
215
|
+
"""
|
|
216
|
+
Get key rotation status.
|
|
217
|
+
|
|
218
|
+
:param client: KMS client
|
|
219
|
+
:param str key_id: Key ID
|
|
220
|
+
:return: Rotation enabled status
|
|
221
|
+
:rtype: bool
|
|
222
|
+
"""
|
|
223
|
+
try:
|
|
224
|
+
response = client.get_key_rotation_status(KeyId=key_id)
|
|
225
|
+
return response.get("KeyRotationEnabled", False)
|
|
226
|
+
except ClientError as e:
|
|
227
|
+
if e.response["Error"]["Code"] in [
|
|
228
|
+
"NotFoundException",
|
|
229
|
+
"AccessDeniedException",
|
|
230
|
+
"UnsupportedOperationException",
|
|
231
|
+
]:
|
|
232
|
+
return False
|
|
233
|
+
logger.debug(f"Error getting rotation status for key {key_id}: {e}")
|
|
234
|
+
return False
|
|
235
|
+
|
|
236
|
+
def _get_key_policy(self, client: Any, key_id: str) -> Optional[str]:
|
|
237
|
+
"""
|
|
238
|
+
Get key policy.
|
|
239
|
+
|
|
240
|
+
:param client: KMS client
|
|
241
|
+
:param str key_id: Key ID
|
|
242
|
+
:return: Key policy as JSON string
|
|
243
|
+
:rtype: Optional[str]
|
|
244
|
+
"""
|
|
245
|
+
try:
|
|
246
|
+
response = client.get_key_policy(KeyId=key_id, PolicyName="default")
|
|
247
|
+
return response.get("Policy")
|
|
248
|
+
except ClientError as e:
|
|
249
|
+
if e.response["Error"]["Code"] not in ["NotFoundException", "AccessDeniedException"]:
|
|
250
|
+
logger.debug(f"Error getting policy for key {key_id}: {e}")
|
|
251
|
+
return None
|
|
252
|
+
|
|
253
|
+
def _list_grants(self, client: Any, key_id: str) -> List[Dict[str, Any]]:
|
|
254
|
+
"""
|
|
255
|
+
List grants for a key.
|
|
256
|
+
|
|
257
|
+
:param client: KMS client
|
|
258
|
+
:param str key_id: Key ID
|
|
259
|
+
:return: List of grants
|
|
260
|
+
:rtype: List[Dict[str, Any]]
|
|
261
|
+
"""
|
|
262
|
+
grants = []
|
|
263
|
+
try:
|
|
264
|
+
paginator = client.get_paginator("list_grants")
|
|
265
|
+
|
|
266
|
+
for page in paginator.paginate(KeyId=key_id):
|
|
267
|
+
grants.extend(page.get("Grants", []))
|
|
268
|
+
|
|
269
|
+
except ClientError as e:
|
|
270
|
+
if e.response["Error"]["Code"] not in ["NotFoundException", "AccessDeniedException"]:
|
|
271
|
+
logger.debug(f"Error listing grants for key {key_id}: {e}")
|
|
272
|
+
|
|
273
|
+
return grants
|
|
274
|
+
|
|
275
|
+
def _list_resource_tags(self, client: Any, key_id: str) -> List[Dict[str, str]]:
|
|
276
|
+
"""
|
|
277
|
+
List tags for a key.
|
|
278
|
+
|
|
279
|
+
:param client: KMS client
|
|
280
|
+
:param str key_id: Key ID
|
|
281
|
+
:return: List of tags
|
|
282
|
+
:rtype: List[Dict[str, str]]
|
|
283
|
+
"""
|
|
284
|
+
try:
|
|
285
|
+
response = client.list_resource_tags(KeyId=key_id)
|
|
286
|
+
return response.get("Tags", [])
|
|
287
|
+
except ClientError as e:
|
|
288
|
+
if e.response["Error"]["Code"] not in ["NotFoundException", "AccessDeniedException"]:
|
|
289
|
+
logger.debug(f"Error listing tags for key {key_id}: {e}")
|
|
290
|
+
return []
|
|
291
|
+
|
|
292
|
+
def _list_aliases(self, client: Any) -> List[Dict[str, Any]]:
|
|
293
|
+
"""
|
|
294
|
+
List KMS aliases.
|
|
295
|
+
|
|
296
|
+
:param client: KMS client
|
|
297
|
+
:return: List of aliases
|
|
298
|
+
:rtype: List[Dict[str, Any]]
|
|
299
|
+
"""
|
|
300
|
+
aliases = []
|
|
301
|
+
try:
|
|
302
|
+
paginator = client.get_paginator("list_aliases")
|
|
303
|
+
|
|
304
|
+
for page in paginator.paginate():
|
|
305
|
+
for alias in page.get("Aliases", []):
|
|
306
|
+
processed_alias = self._process_alias(client, alias)
|
|
307
|
+
if processed_alias:
|
|
308
|
+
aliases.append(processed_alias)
|
|
309
|
+
|
|
310
|
+
except ClientError as e:
|
|
311
|
+
self._handle_list_aliases_error(e)
|
|
312
|
+
|
|
313
|
+
return aliases
|
|
314
|
+
|
|
315
|
+
def _process_alias(self, client: Any, alias: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
316
|
+
"""
|
|
317
|
+
Process a single KMS alias and return its details if it passes filters.
|
|
318
|
+
|
|
319
|
+
:param client: KMS client
|
|
320
|
+
:param dict alias: Alias information from AWS API
|
|
321
|
+
:return: Alias dictionary if it passes filters, None otherwise
|
|
322
|
+
:rtype: Optional[Dict[str, Any]]
|
|
323
|
+
"""
|
|
324
|
+
if not self._alias_passes_filters(client, alias):
|
|
325
|
+
return None
|
|
326
|
+
|
|
327
|
+
return {
|
|
328
|
+
"Region": self.region,
|
|
329
|
+
"AliasName": alias.get("AliasName"),
|
|
330
|
+
"AliasArn": alias.get("AliasArn"),
|
|
331
|
+
"TargetKeyId": alias.get("TargetKeyId"),
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
def _alias_passes_filters(self, client: Any, alias: Dict[str, Any]) -> bool:
|
|
335
|
+
"""
|
|
336
|
+
Check if alias passes account filtering rules.
|
|
337
|
+
|
|
338
|
+
:param client: KMS client
|
|
339
|
+
:param dict alias: Alias information
|
|
340
|
+
:return: True if alias passes filters
|
|
341
|
+
:rtype: bool
|
|
342
|
+
"""
|
|
343
|
+
if not self.account_id:
|
|
344
|
+
return True
|
|
345
|
+
|
|
346
|
+
if self._is_aws_managed_alias(alias):
|
|
347
|
+
return False
|
|
348
|
+
|
|
349
|
+
return self._alias_target_matches_account(client, alias)
|
|
350
|
+
|
|
351
|
+
def _is_aws_managed_alias(self, alias: Dict[str, Any]) -> bool:
|
|
352
|
+
"""
|
|
353
|
+
Check if alias is AWS-managed.
|
|
354
|
+
|
|
355
|
+
:param dict alias: Alias information
|
|
356
|
+
:return: True if alias is AWS-managed
|
|
357
|
+
:rtype: bool
|
|
358
|
+
"""
|
|
359
|
+
alias_name = alias.get("AliasName", "")
|
|
360
|
+
return alias_name.startswith("alias/aws/")
|
|
361
|
+
|
|
362
|
+
def _alias_target_matches_account(self, client: Any, alias: Dict[str, Any]) -> bool:
|
|
363
|
+
"""
|
|
364
|
+
Check if alias target key matches the account filter.
|
|
365
|
+
|
|
366
|
+
:param client: KMS client
|
|
367
|
+
:param dict alias: Alias information
|
|
368
|
+
:return: True if target key matches account or no target key exists
|
|
369
|
+
:rtype: bool
|
|
370
|
+
"""
|
|
371
|
+
target_key_id = alias.get("TargetKeyId")
|
|
372
|
+
if not target_key_id:
|
|
373
|
+
return True
|
|
374
|
+
|
|
375
|
+
key_info = self._describe_key(client, target_key_id)
|
|
376
|
+
if not key_info:
|
|
377
|
+
return True
|
|
378
|
+
|
|
379
|
+
return self._matches_account_id(key_info.get("Arn", ""))
|
|
380
|
+
|
|
381
|
+
def _handle_list_aliases_error(self, error: ClientError) -> None:
|
|
382
|
+
"""
|
|
383
|
+
Handle errors from list_aliases operation.
|
|
384
|
+
|
|
385
|
+
:param error: Client error exception
|
|
386
|
+
"""
|
|
387
|
+
if error.response["Error"]["Code"] == "AccessDeniedException":
|
|
388
|
+
logger.warning(f"Access denied to list KMS aliases in {self.region}")
|
|
389
|
+
else:
|
|
390
|
+
logger.error(f"Error listing KMS aliases: {error}")
|
|
391
|
+
|
|
392
|
+
def _matches_account_id(self, arn: str) -> bool:
|
|
393
|
+
"""
|
|
394
|
+
Check if ARN matches the specified account ID.
|
|
395
|
+
|
|
396
|
+
:param str arn: ARN to check
|
|
397
|
+
:return: True if matches or no account_id filter specified
|
|
398
|
+
:rtype: bool
|
|
399
|
+
"""
|
|
400
|
+
if not self.account_id:
|
|
401
|
+
return True
|
|
402
|
+
|
|
403
|
+
# ARN format: arn:aws:kms:region:account-id:key/key-id
|
|
404
|
+
try:
|
|
405
|
+
arn_parts = arn.split(":")
|
|
406
|
+
if len(arn_parts) >= 5:
|
|
407
|
+
key_account_id = arn_parts[4]
|
|
408
|
+
return key_account_id == self.account_id
|
|
409
|
+
except (IndexError, AttributeError):
|
|
410
|
+
logger.warning(f"Could not parse account ID from KMS key ARN: {arn}")
|
|
411
|
+
|
|
412
|
+
return False
|
|
413
|
+
|
|
414
|
+
def _get_key_tags(self, client: Any, key_id: str) -> Dict[str, str]:
|
|
415
|
+
"""
|
|
416
|
+
Get tags for a KMS key.
|
|
417
|
+
|
|
418
|
+
:param client: KMS client
|
|
419
|
+
:param str key_id: Key ID
|
|
420
|
+
:return: Dictionary of tags (Key -> Value)
|
|
421
|
+
:rtype: Dict[str, str]
|
|
422
|
+
"""
|
|
423
|
+
try:
|
|
424
|
+
response = client.list_resource_tags(KeyId=key_id)
|
|
425
|
+
tags_list = response.get("Tags", [])
|
|
426
|
+
return {tag["TagKey"]: tag["TagValue"] for tag in tags_list}
|
|
427
|
+
except ClientError as e:
|
|
428
|
+
logger.debug(f"Error getting tags for key {key_id}: {e}")
|
|
429
|
+
return {}
|
|
430
|
+
|
|
431
|
+
def _matches_tags(self, resource_tags: Dict[str, str]) -> bool:
|
|
432
|
+
"""
|
|
433
|
+
Check if resource tags match the specified filter tags.
|
|
434
|
+
|
|
435
|
+
:param dict resource_tags: Tags on the resource
|
|
436
|
+
:return: True if all filter tags match
|
|
437
|
+
:rtype: bool
|
|
438
|
+
"""
|
|
439
|
+
if not self.tags:
|
|
440
|
+
return True
|
|
441
|
+
|
|
442
|
+
# All filter tags must match
|
|
443
|
+
for key, value in self.tags.items():
|
|
444
|
+
if resource_tags.get(key) != value:
|
|
445
|
+
return False
|
|
446
|
+
|
|
447
|
+
return True
|