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,432 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Control Matcher - A utility class for identifying and matching control implementations
|
|
5
|
+
across different RegScale entities based on control ID strings.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import re
|
|
10
|
+
from typing import Dict, List, Optional, Tuple
|
|
11
|
+
|
|
12
|
+
from regscale.core.app.api import Api
|
|
13
|
+
from regscale.core.app.application import Application
|
|
14
|
+
from regscale.models.regscale_models.control_implementation import ControlImplementation
|
|
15
|
+
from regscale.models.regscale_models.security_control import SecurityControl
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger("regscale")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ControlMatcher:
|
|
21
|
+
"""
|
|
22
|
+
A class to identify control IDs and match them across different RegScale entities.
|
|
23
|
+
|
|
24
|
+
This class provides control matching capabilities:
|
|
25
|
+
- Parse control ID strings to extract NIST control identifiers
|
|
26
|
+
- Match controls from catalogs to security plans
|
|
27
|
+
- Find control implementations based on control IDs
|
|
28
|
+
- Support multiple control ID formats (e.g., AC-1, AC-1(1), AC-1.1)
|
|
29
|
+
|
|
30
|
+
Note: This class is focused on finding and matching existing controls only.
|
|
31
|
+
Control creation/modification should be handled by calling code.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, app: Optional[Application] = None):
|
|
35
|
+
"""
|
|
36
|
+
Initialize the ControlMatcher.
|
|
37
|
+
|
|
38
|
+
:param Optional[Application] app: RegScale Application instance
|
|
39
|
+
"""
|
|
40
|
+
self.app = app or Application()
|
|
41
|
+
self.api = Api()
|
|
42
|
+
self._catalog_cache: Dict[int, List[SecurityControl]] = {}
|
|
43
|
+
self._control_impl_cache: Dict[Tuple[int, str], Dict[str, ControlImplementation]] = {}
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def parse_control_id(control_string: str) -> Optional[str]:
|
|
47
|
+
"""
|
|
48
|
+
Parse a control ID string and extract the standardized control identifier.
|
|
49
|
+
|
|
50
|
+
Handles various formats:
|
|
51
|
+
- NIST format: AC-1, AC-1(1), AC-1.1, AC-1(a), AC-1.a
|
|
52
|
+
- With leading zeros: AC-01, AC-17(02)
|
|
53
|
+
- With spaces: AC-1 (1), AC-02 (04), AC-1 (a)
|
|
54
|
+
- With text: "Access Control AC-1"
|
|
55
|
+
- Multiple controls: "AC-1, AC-2"
|
|
56
|
+
|
|
57
|
+
:param str control_string: Raw control ID string
|
|
58
|
+
:return: Standardized control ID or None if not found
|
|
59
|
+
:rtype: Optional[str]
|
|
60
|
+
"""
|
|
61
|
+
if not control_string:
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
# Clean the string
|
|
65
|
+
control_string = control_string.strip().upper()
|
|
66
|
+
|
|
67
|
+
# Common NIST control ID pattern
|
|
68
|
+
# Matches: AC-1, AC-01, AC-1(1), AC-1(01), AC-1 (1), AC-1.1, AC-1.01, AC-1(a), AC-1.a, etc.
|
|
69
|
+
# Allows optional whitespace before and inside parentheses
|
|
70
|
+
# Now includes letter parts (a, b, c) in addition to numeric parts
|
|
71
|
+
pattern = r"([A-Z]{2,3}-\d+(?:\s*\(\s*(?:\d+|[A-Z])\s*\)|\.(?:\d+|[A-Z]))?)"
|
|
72
|
+
|
|
73
|
+
if matches := re.findall(pattern, control_string):
|
|
74
|
+
# Normalize parentheses to dots for consistency and remove spaces
|
|
75
|
+
control_id = matches[0]
|
|
76
|
+
control_id = control_id.replace(" ", "") # Remove all spaces
|
|
77
|
+
control_id = control_id.replace("(", ".").replace(")", "")
|
|
78
|
+
|
|
79
|
+
# Normalize leading zeros (e.g., AC-01.02 -> AC-1.2)
|
|
80
|
+
parts = control_id.split("-")
|
|
81
|
+
if len(parts) == 2:
|
|
82
|
+
family = parts[0]
|
|
83
|
+
number_part = parts[1]
|
|
84
|
+
|
|
85
|
+
if "." in number_part:
|
|
86
|
+
main_num, enhancement = number_part.split(".", 1)
|
|
87
|
+
main_num = str(int(main_num))
|
|
88
|
+
# Only normalize if enhancement is numeric, preserve letters as-is
|
|
89
|
+
if enhancement.isdigit():
|
|
90
|
+
enhancement = str(int(enhancement))
|
|
91
|
+
control_id = f"{family}-{main_num}.{enhancement}"
|
|
92
|
+
else:
|
|
93
|
+
main_num = str(int(number_part))
|
|
94
|
+
control_id = f"{family}-{main_num}"
|
|
95
|
+
|
|
96
|
+
return control_id
|
|
97
|
+
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
def find_control_in_catalog(self, control_id: str, catalog_id: int) -> Optional[SecurityControl]:
|
|
101
|
+
"""
|
|
102
|
+
Find a security control in a specific catalog by control ID.
|
|
103
|
+
|
|
104
|
+
:param str control_id: The control ID to search for
|
|
105
|
+
:param int catalog_id: The catalog ID to search in
|
|
106
|
+
:return: SecurityControl object if found, None otherwise
|
|
107
|
+
:rtype: Optional[SecurityControl]
|
|
108
|
+
"""
|
|
109
|
+
controls = self._get_catalog_controls(catalog_id)
|
|
110
|
+
|
|
111
|
+
# Generate all possible variations of the control ID
|
|
112
|
+
search_ids = self._get_control_id_variations(control_id)
|
|
113
|
+
|
|
114
|
+
# Try exact match with any variation
|
|
115
|
+
for control in controls:
|
|
116
|
+
if control.controlId in search_ids:
|
|
117
|
+
return control
|
|
118
|
+
|
|
119
|
+
# Try matching control variations against search variations
|
|
120
|
+
for control in controls:
|
|
121
|
+
control_variations = self._get_control_id_variations(control.controlId)
|
|
122
|
+
if control_variations & search_ids: # Set intersection
|
|
123
|
+
return control
|
|
124
|
+
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
def find_control_implementation(
|
|
128
|
+
self, control_id: str, parent_id: int, parent_module: str = "securityplans", catalog_id: Optional[int] = None
|
|
129
|
+
) -> Optional[ControlImplementation]:
|
|
130
|
+
"""
|
|
131
|
+
Find a control implementation based on control ID and parent context.
|
|
132
|
+
|
|
133
|
+
:param str control_id: The control ID to match
|
|
134
|
+
:param int parent_id: Parent entity ID (e.g., security plan ID)
|
|
135
|
+
:param str parent_module: Parent module type (default: securityplans)
|
|
136
|
+
:param Optional[int] catalog_id: Optional catalog ID for better matching
|
|
137
|
+
:return: ControlImplementation if found, None otherwise
|
|
138
|
+
:rtype: Optional[ControlImplementation]
|
|
139
|
+
"""
|
|
140
|
+
# Get control implementations for the parent
|
|
141
|
+
implementations = self._get_control_implementations(parent_id, parent_module)
|
|
142
|
+
|
|
143
|
+
# Get all variations of the control ID for matching
|
|
144
|
+
search_variations = self._get_control_id_variations(control_id)
|
|
145
|
+
if not search_variations:
|
|
146
|
+
logger.warning(f"Could not parse control ID: {control_id}")
|
|
147
|
+
return None
|
|
148
|
+
|
|
149
|
+
# Try to find matching implementation with variation matching
|
|
150
|
+
for impl_key, impl in implementations.items():
|
|
151
|
+
impl_variations = self._get_control_id_variations(impl_key)
|
|
152
|
+
if impl_variations & search_variations: # Set intersection
|
|
153
|
+
return impl
|
|
154
|
+
|
|
155
|
+
# If catalog ID provided, try to find via security control
|
|
156
|
+
if catalog_id:
|
|
157
|
+
control = self.find_control_in_catalog(control_id, catalog_id)
|
|
158
|
+
if control:
|
|
159
|
+
# Search implementations by control ID
|
|
160
|
+
for impl in implementations.values():
|
|
161
|
+
if impl.controlID == control.id:
|
|
162
|
+
return impl
|
|
163
|
+
|
|
164
|
+
return None
|
|
165
|
+
|
|
166
|
+
def match_controls_to_implementations(
|
|
167
|
+
self,
|
|
168
|
+
control_ids: List[str],
|
|
169
|
+
parent_id: int,
|
|
170
|
+
parent_module: str = "securityplans",
|
|
171
|
+
catalog_id: Optional[int] = None,
|
|
172
|
+
) -> Dict[str, Optional[ControlImplementation]]:
|
|
173
|
+
"""
|
|
174
|
+
Match multiple control IDs to their implementations.
|
|
175
|
+
|
|
176
|
+
:param List[str] control_ids: List of control ID strings
|
|
177
|
+
:param int parent_id: Parent entity ID
|
|
178
|
+
:param str parent_module: Parent module type
|
|
179
|
+
:param Optional[int] catalog_id: Optional catalog ID
|
|
180
|
+
:return: Dictionary mapping control IDs to implementations
|
|
181
|
+
:rtype: Dict[str, Optional[ControlImplementation]]
|
|
182
|
+
"""
|
|
183
|
+
results = {}
|
|
184
|
+
|
|
185
|
+
for control_id in control_ids:
|
|
186
|
+
impl = self.find_control_implementation(control_id, parent_id, parent_module, catalog_id)
|
|
187
|
+
results[control_id] = impl
|
|
188
|
+
|
|
189
|
+
return results
|
|
190
|
+
|
|
191
|
+
def get_security_plan_controls(self, security_plan_id: int) -> Dict[str, ControlImplementation]:
|
|
192
|
+
"""
|
|
193
|
+
Get all control implementations for a security plan.
|
|
194
|
+
|
|
195
|
+
:param int security_plan_id: The security plan ID
|
|
196
|
+
:return: Dictionary of control implementations keyed by control ID
|
|
197
|
+
:rtype: Dict[str, ControlImplementation]
|
|
198
|
+
"""
|
|
199
|
+
return self._get_control_implementations(security_plan_id, "securityplans")
|
|
200
|
+
|
|
201
|
+
def find_controls_by_pattern(self, pattern: str, catalog_id: int) -> List[SecurityControl]:
|
|
202
|
+
"""
|
|
203
|
+
Find all controls in a catalog matching a pattern.
|
|
204
|
+
|
|
205
|
+
:param str pattern: Regex pattern or substring to match
|
|
206
|
+
:param int catalog_id: Catalog ID to search in
|
|
207
|
+
:return: List of matching SecurityControl objects
|
|
208
|
+
:rtype: List[SecurityControl]
|
|
209
|
+
"""
|
|
210
|
+
controls = self._get_catalog_controls(catalog_id)
|
|
211
|
+
matched = []
|
|
212
|
+
|
|
213
|
+
for control in controls:
|
|
214
|
+
if (re.search(pattern, control.controlId, re.IGNORECASE)) or (
|
|
215
|
+
control.title and re.search(pattern, control.title, re.IGNORECASE)
|
|
216
|
+
):
|
|
217
|
+
matched.append(control)
|
|
218
|
+
|
|
219
|
+
return matched
|
|
220
|
+
|
|
221
|
+
def bulk_match_controls(
|
|
222
|
+
self,
|
|
223
|
+
control_mappings: Dict[str, str],
|
|
224
|
+
parent_id: int,
|
|
225
|
+
parent_module: str = "securityplans",
|
|
226
|
+
catalog_id: Optional[int] = None,
|
|
227
|
+
) -> Dict[str, Optional[ControlImplementation]]:
|
|
228
|
+
"""
|
|
229
|
+
Bulk match control IDs to their implementations.
|
|
230
|
+
|
|
231
|
+
:param Dict[str, str] control_mappings: Dict of {external_id: control_id}
|
|
232
|
+
:param int parent_id: Parent entity ID
|
|
233
|
+
:param str parent_module: Parent module type
|
|
234
|
+
:param Optional[int] catalog_id: Catalog ID for controls
|
|
235
|
+
:return: Dictionary mapping external IDs to ControlImplementations (None if not found)
|
|
236
|
+
:rtype: Dict[str, Optional[ControlImplementation]]
|
|
237
|
+
"""
|
|
238
|
+
results = {}
|
|
239
|
+
|
|
240
|
+
for external_id, control_id in control_mappings.items():
|
|
241
|
+
impl = self.find_control_implementation(control_id, parent_id, parent_module, catalog_id)
|
|
242
|
+
results[external_id] = impl
|
|
243
|
+
|
|
244
|
+
return results
|
|
245
|
+
|
|
246
|
+
def _get_catalog_controls(self, catalog_id: int) -> List[SecurityControl]:
|
|
247
|
+
"""
|
|
248
|
+
Get all controls for a catalog (with caching).
|
|
249
|
+
|
|
250
|
+
:param int catalog_id: Catalog ID
|
|
251
|
+
:return: List of SecurityControl objects
|
|
252
|
+
:rtype: List[SecurityControl]
|
|
253
|
+
"""
|
|
254
|
+
if catalog_id not in self._catalog_cache:
|
|
255
|
+
try:
|
|
256
|
+
controls = SecurityControl.get_list_by_catalog(catalog_id)
|
|
257
|
+
self._catalog_cache[catalog_id] = controls
|
|
258
|
+
except Exception as e:
|
|
259
|
+
logger.error(f"Failed to get controls for catalog {catalog_id}: {e}")
|
|
260
|
+
return []
|
|
261
|
+
|
|
262
|
+
return self._catalog_cache.get(catalog_id, [])
|
|
263
|
+
|
|
264
|
+
def _normalize_control_id(self, control_id: str) -> Optional[str]:
|
|
265
|
+
"""
|
|
266
|
+
Normalize a control ID by removing leading zeros from all numeric parts.
|
|
267
|
+
|
|
268
|
+
Examples:
|
|
269
|
+
- AC-01 -> AC-1
|
|
270
|
+
- AC-17(02) -> AC-17.2
|
|
271
|
+
- AC-1.01 -> AC-1.1
|
|
272
|
+
|
|
273
|
+
:param str control_id: The control ID to normalize
|
|
274
|
+
:return: Normalized control ID or None if invalid
|
|
275
|
+
:rtype: Optional[str]
|
|
276
|
+
"""
|
|
277
|
+
parsed = self.parse_control_id(control_id)
|
|
278
|
+
if not parsed:
|
|
279
|
+
return None
|
|
280
|
+
|
|
281
|
+
# Split by '-' to get family and number parts
|
|
282
|
+
parts = parsed.split("-")
|
|
283
|
+
if len(parts) != 2:
|
|
284
|
+
return None
|
|
285
|
+
|
|
286
|
+
family = parts[0]
|
|
287
|
+
number_part = parts[1]
|
|
288
|
+
|
|
289
|
+
# Handle enhancement notation (both . and parentheses are normalized to .)
|
|
290
|
+
if "." in number_part:
|
|
291
|
+
main_num, enhancement = number_part.split(".", 1)
|
|
292
|
+
# Remove leading zeros from both parts
|
|
293
|
+
main_num = str(int(main_num))
|
|
294
|
+
enhancement = str(int(enhancement))
|
|
295
|
+
return f"{family}-{main_num}.{enhancement}"
|
|
296
|
+
else:
|
|
297
|
+
# Just main control number
|
|
298
|
+
main_num = str(int(number_part))
|
|
299
|
+
return f"{family}-{main_num}"
|
|
300
|
+
|
|
301
|
+
@staticmethod
|
|
302
|
+
def _generate_simple_variations(family: str, main_num: str) -> set:
|
|
303
|
+
"""
|
|
304
|
+
Generate variations for simple control IDs without enhancements.
|
|
305
|
+
|
|
306
|
+
:param str family: Control family (e.g., AC, SI)
|
|
307
|
+
:param str main_num: Main control number
|
|
308
|
+
:return: Set of variations
|
|
309
|
+
:rtype: set
|
|
310
|
+
"""
|
|
311
|
+
main_int = int(main_num)
|
|
312
|
+
return {
|
|
313
|
+
f"{family}-{main_int}",
|
|
314
|
+
f"{family}-{main_int:02d}",
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
@staticmethod
|
|
318
|
+
def _generate_letter_enhancement_variations(family: str, main_num: str, enhancement: str) -> set:
|
|
319
|
+
"""
|
|
320
|
+
Generate variations for control IDs with letter-based enhancements.
|
|
321
|
+
|
|
322
|
+
:param str family: Control family (e.g., AC, SI)
|
|
323
|
+
:param str main_num: Main control number
|
|
324
|
+
:param str enhancement: Letter enhancement (e.g., a, b)
|
|
325
|
+
:return: Set of variations
|
|
326
|
+
:rtype: set
|
|
327
|
+
"""
|
|
328
|
+
main_int = int(main_num)
|
|
329
|
+
variations = set()
|
|
330
|
+
|
|
331
|
+
for main_fmt in [str(main_int), f"{main_int:02d}"]:
|
|
332
|
+
variations.add(f"{family}-{main_fmt}.{enhancement}")
|
|
333
|
+
variations.add(f"{family}-{main_fmt}({enhancement})")
|
|
334
|
+
|
|
335
|
+
return variations
|
|
336
|
+
|
|
337
|
+
@staticmethod
|
|
338
|
+
def _generate_numeric_enhancement_variations(family: str, main_num: str, enhancement: str) -> set:
|
|
339
|
+
"""
|
|
340
|
+
Generate variations for control IDs with numeric enhancements.
|
|
341
|
+
|
|
342
|
+
:param str family: Control family (e.g., AC, SI)
|
|
343
|
+
:param str main_num: Main control number
|
|
344
|
+
:param str enhancement: Numeric enhancement
|
|
345
|
+
:return: Set of variations
|
|
346
|
+
:rtype: set
|
|
347
|
+
"""
|
|
348
|
+
main_int = int(main_num)
|
|
349
|
+
enh_int = int(enhancement)
|
|
350
|
+
variations = set()
|
|
351
|
+
|
|
352
|
+
for main_fmt in [str(main_int), f"{main_int:02d}"]:
|
|
353
|
+
for enh_fmt in [str(enh_int), f"{enh_int:02d}"]:
|
|
354
|
+
variations.add(f"{family}-{main_fmt}.{enh_fmt}")
|
|
355
|
+
variations.add(f"{family}-{main_fmt}({enh_fmt})")
|
|
356
|
+
|
|
357
|
+
return variations
|
|
358
|
+
|
|
359
|
+
def _get_control_id_variations(self, control_id: str) -> set:
|
|
360
|
+
"""
|
|
361
|
+
Generate all valid variations of a control ID (with and without leading zeros).
|
|
362
|
+
|
|
363
|
+
Examples:
|
|
364
|
+
- AC-1 -> {AC-1, AC-01}
|
|
365
|
+
- AC-17.2 -> {AC-17.2, AC-17.02, AC-17(2), AC-17(02)}
|
|
366
|
+
- AC-1.a -> {AC-1.a, AC-01.a, AC-1(a), AC-01(a)}
|
|
367
|
+
|
|
368
|
+
:param str control_id: The control ID to generate variations for
|
|
369
|
+
:return: Set of all valid variations
|
|
370
|
+
:rtype: set
|
|
371
|
+
"""
|
|
372
|
+
parsed = self.parse_control_id(control_id)
|
|
373
|
+
if not parsed:
|
|
374
|
+
return set()
|
|
375
|
+
|
|
376
|
+
# Split by '-' to get family and number parts
|
|
377
|
+
parts = parsed.split("-")
|
|
378
|
+
if len(parts) != 2:
|
|
379
|
+
return set()
|
|
380
|
+
|
|
381
|
+
family = parts[0]
|
|
382
|
+
number_part = parts[1]
|
|
383
|
+
|
|
384
|
+
# Handle enhancement notation
|
|
385
|
+
if "." in number_part:
|
|
386
|
+
main_num, enhancement = number_part.split(".", 1)
|
|
387
|
+
|
|
388
|
+
# Check if enhancement is a letter (a, b, c, etc.) or a number
|
|
389
|
+
if enhancement.isalpha():
|
|
390
|
+
variations = self._generate_letter_enhancement_variations(family, main_num, enhancement)
|
|
391
|
+
else:
|
|
392
|
+
variations = self._generate_numeric_enhancement_variations(family, main_num, enhancement)
|
|
393
|
+
else:
|
|
394
|
+
variations = self._generate_simple_variations(family, number_part)
|
|
395
|
+
|
|
396
|
+
# Add uppercase versions to ensure consistency
|
|
397
|
+
return {v.upper() for v in variations}
|
|
398
|
+
|
|
399
|
+
def _get_control_implementations(self, parent_id: int, parent_module: str) -> Dict[str, ControlImplementation]:
|
|
400
|
+
"""
|
|
401
|
+
Get control implementations for a parent (with caching).
|
|
402
|
+
|
|
403
|
+
:param int parent_id: Parent ID
|
|
404
|
+
:param str parent_module: Parent module
|
|
405
|
+
:return: Dict of implementations keyed by control ID
|
|
406
|
+
:rtype: Dict[str, ControlImplementation]
|
|
407
|
+
"""
|
|
408
|
+
cache_key = (parent_id, parent_module)
|
|
409
|
+
|
|
410
|
+
if cache_key not in self._control_impl_cache:
|
|
411
|
+
try:
|
|
412
|
+
# Get the label map which maps control IDs to implementation IDs
|
|
413
|
+
label_map = ControlImplementation.get_control_label_map_by_parent(parent_id, parent_module)
|
|
414
|
+
|
|
415
|
+
implementations = {}
|
|
416
|
+
for control_label, impl_id in label_map.items():
|
|
417
|
+
impl = ControlImplementation.get_object(impl_id)
|
|
418
|
+
if impl:
|
|
419
|
+
implementations[control_label] = impl
|
|
420
|
+
|
|
421
|
+
self._control_impl_cache[cache_key] = implementations
|
|
422
|
+
except Exception as e:
|
|
423
|
+
logger.error(f"Failed to get implementations for {parent_module}/{parent_id}: {e}")
|
|
424
|
+
return {}
|
|
425
|
+
|
|
426
|
+
return self._control_impl_cache.get(cache_key, {})
|
|
427
|
+
|
|
428
|
+
def clear_cache(self):
|
|
429
|
+
"""Clear all cached data."""
|
|
430
|
+
self._catalog_cache.clear()
|
|
431
|
+
self._control_impl_cache.clear()
|
|
432
|
+
logger.info("Cleared control matcher cache")
|