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
|
+
"""
|
|
2
|
+
Module to import and convert SARIF files to OCSF format using an API converter, and creating Vulnerability,
|
|
3
|
+
VulnerabilityMapping, and ScanHistory objects in RegScale.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import datetime
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
from logging import getLogger
|
|
10
|
+
from typing import Any, Dict, Optional, Union
|
|
11
|
+
|
|
12
|
+
import requests
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from requests.exceptions import RequestException
|
|
15
|
+
from rich.progress import Progress
|
|
16
|
+
from synqly.engine.resources.ocsf.resources.v_1_3_0.resources.vulnerabilityfinding import Vulnerability as OcsfVuln
|
|
17
|
+
from synqly.engine.resources.ocsf.resources.v_1_5_0.resources.applicationsecurityposturefinding import (
|
|
18
|
+
ApplicationSecurityPostureFinding,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
from regscale.core.app.application import Application
|
|
22
|
+
from regscale.core.app.utils.app_utils import (
|
|
23
|
+
create_progress_object,
|
|
24
|
+
check_file_path,
|
|
25
|
+
check_license,
|
|
26
|
+
error_and_exit,
|
|
27
|
+
save_data_to,
|
|
28
|
+
get_current_datetime,
|
|
29
|
+
)
|
|
30
|
+
from regscale.models import Asset, IssueStatus, Issue, ScanHistory, SecurityPlan, Vulnerability, VulnerabilityMapping
|
|
31
|
+
from regscale.validation.record import validate_regscale_object
|
|
32
|
+
|
|
33
|
+
logger = getLogger("regscale")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class SarifImporter:
|
|
37
|
+
"""
|
|
38
|
+
Class to handle importing and converting SARIF files to OCSF format using an API converter. It then creates
|
|
39
|
+
Vulnerability, VulnerabilityMapping, ScanHistory objects in RegScale.
|
|
40
|
+
|
|
41
|
+
:param Path file_path: Path to the SARIF file or directory of files
|
|
42
|
+
:param int asset_id: The RegScale Asset ID to import the findings to
|
|
43
|
+
:param Union[datetime.datetime, str, None] scan_date: The scan date of the file (defaults to current date if None)
|
|
44
|
+
:rtype: None
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(self, file_path: Path, asset_id: int, scan_date: Union[datetime.datetime, str, None] = None) -> None:
|
|
48
|
+
self.file_path: Path = file_path
|
|
49
|
+
self.app: Application = check_license()
|
|
50
|
+
if not scan_date:
|
|
51
|
+
scan_date = get_current_datetime()
|
|
52
|
+
if isinstance(scan_date, datetime.datetime):
|
|
53
|
+
scan_date = scan_date.strftime("%Y-%m-%d")
|
|
54
|
+
self.scan_date: str = scan_date
|
|
55
|
+
self.token: Optional[str] = os.getenv("synqlyAccessToken") or self.app.config.get("synqlyAccessToken")
|
|
56
|
+
if not self.token:
|
|
57
|
+
error_and_exit("synqlyAccessToken environment variable or init.yaml value is required")
|
|
58
|
+
if not validate_regscale_object(asset_id, Asset.get_module_string()):
|
|
59
|
+
error_and_exit(f"Asset ID {asset_id} does not exist in RegScale.")
|
|
60
|
+
self.asset: Asset = Asset.get_object(asset_id)
|
|
61
|
+
self.parent_id = asset_id
|
|
62
|
+
self.parent_module = Asset.get_module_slug()
|
|
63
|
+
self.scan_history: Optional[ScanHistory] = None
|
|
64
|
+
self.progress: Progress = create_progress_object()
|
|
65
|
+
self.process_sarif_files()
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
def load_sarif_file(file_path: Path) -> Dict[str, Any]:
|
|
69
|
+
"""
|
|
70
|
+
Load and parse a SARIF file from disk
|
|
71
|
+
|
|
72
|
+
:param Path file_path: Path to the SARIF file
|
|
73
|
+
:return: Parsed SARIF data as dictionary
|
|
74
|
+
:rtype: Dict[str, Any]
|
|
75
|
+
:raises: SystemExit if file cannot be loaded or parsed
|
|
76
|
+
"""
|
|
77
|
+
try:
|
|
78
|
+
with open(file_path, "r", encoding="utf-8") as file: # type: ignore
|
|
79
|
+
sarif_data = json.load(file)
|
|
80
|
+
|
|
81
|
+
# Basic validation that it's a SARIF file
|
|
82
|
+
if not isinstance(sarif_data, dict) or "$schema" not in sarif_data:
|
|
83
|
+
logger.warning("File may not be a valid SARIF file (missing $schema)")
|
|
84
|
+
elif "sarif" not in sarif_data.get("$schema", "").lower():
|
|
85
|
+
logger.warning("File may not be a valid SARIF file (schema doesn't contain 'sarif')")
|
|
86
|
+
|
|
87
|
+
logger.debug("Successfully loaded SARIF file with %d runs", len(sarif_data.get("runs", [])))
|
|
88
|
+
return sarif_data
|
|
89
|
+
|
|
90
|
+
except FileNotFoundError:
|
|
91
|
+
error_and_exit(f"SARIF file not found: {file_path}")
|
|
92
|
+
except json.JSONDecodeError as e:
|
|
93
|
+
error_and_exit(f"Failed to parse SARIF file as JSON: {e}")
|
|
94
|
+
except Exception as e:
|
|
95
|
+
error_and_exit(f"Failed to load SARIF file: {e}")
|
|
96
|
+
|
|
97
|
+
def convert_sarif_to_ocsf(self, sarif_data: Dict[str, Any]) -> Optional[Any]:
|
|
98
|
+
"""
|
|
99
|
+
Convert SARIF data to OCSF format using Synqly API
|
|
100
|
+
|
|
101
|
+
:param Dict[str, Any] sarif_data: The SARIF data to convert
|
|
102
|
+
:raises: SystemExit if API call fails
|
|
103
|
+
:return: Converted data from Synqly API
|
|
104
|
+
:rtype: Optional[Any]
|
|
105
|
+
"""
|
|
106
|
+
api_url = "https://api.synqly.com/v1/mappings/apply"
|
|
107
|
+
|
|
108
|
+
headers = {"content-type": "application/json", "authorization": f"Bearer {self.token}"}
|
|
109
|
+
|
|
110
|
+
payload = {"mappings": ["synqly-default.sarif:1.5.0.0"], "data": sarif_data}
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
logger.debug("Making API request to convert the data: %s", api_url)
|
|
114
|
+
response = requests.post(api_url, headers=headers, json=payload, timeout=60) # 60 second timeout
|
|
115
|
+
|
|
116
|
+
if response.status_code != 200:
|
|
117
|
+
logger.error("API request failed with status %d: %s", response.status_code, response.text)
|
|
118
|
+
|
|
119
|
+
converted_data = response.json()
|
|
120
|
+
logger.debug("Successfully converted SARIF data using API.")
|
|
121
|
+
logger.debug("Response status: %d, Content length: %d bytes", response.status_code, len(response.content))
|
|
122
|
+
|
|
123
|
+
# get the results from the nested dictionary
|
|
124
|
+
if mapping := converted_data.get("result", {}).get("mapping", {}):
|
|
125
|
+
return mapping
|
|
126
|
+
logger.error("Failed to convert SARIF data using API.")
|
|
127
|
+
|
|
128
|
+
except RequestException as e:
|
|
129
|
+
if hasattr(e, "response") and e.response is not None:
|
|
130
|
+
try:
|
|
131
|
+
error_detail = e.response.json()
|
|
132
|
+
error_and_exit(f"API error ({e.response.status_code}): {error_detail}")
|
|
133
|
+
except json.JSONDecodeError:
|
|
134
|
+
error_and_exit(f"API error ({e.response.status_code}): {e.response.text}")
|
|
135
|
+
else:
|
|
136
|
+
error_and_exit(f"Failed to connect to API: {e}")
|
|
137
|
+
except json.JSONDecodeError:
|
|
138
|
+
error_and_exit("Failed to parse API response as JSON")
|
|
139
|
+
except Exception as e:
|
|
140
|
+
error_and_exit(f"Unexpected error during SARIF conversion: {e}")
|
|
141
|
+
|
|
142
|
+
def _process_single_sarif_file(
|
|
143
|
+
self,
|
|
144
|
+
sarif_file: Path,
|
|
145
|
+
output_directory: Path,
|
|
146
|
+
) -> Dict[str, Any]:
|
|
147
|
+
"""
|
|
148
|
+
Process a single SARIF file and return result information
|
|
149
|
+
|
|
150
|
+
:param Path sarif_file: Path to the SARIF file to process
|
|
151
|
+
:param Path output_directory: Output directory for converted files
|
|
152
|
+
:return: Processing result information
|
|
153
|
+
:rtype: Dict[str, Any]
|
|
154
|
+
"""
|
|
155
|
+
try:
|
|
156
|
+
sarif_data = self.load_sarif_file(sarif_file)
|
|
157
|
+
logger.debug(f"Converting {sarif_file} data to OCSF data via API...")
|
|
158
|
+
|
|
159
|
+
if "runs" in sarif_data and isinstance(sarif_data["runs"], list) and len(sarif_data["runs"]) > 0:
|
|
160
|
+
# Process each run separately and collect results
|
|
161
|
+
converted_data = [self.convert_sarif_to_ocsf(run) for run in sarif_data["runs"]]
|
|
162
|
+
else:
|
|
163
|
+
# Process the entire SARIF data as a single unit
|
|
164
|
+
converted_data = self.convert_sarif_to_ocsf(sarif_data)
|
|
165
|
+
|
|
166
|
+
# Save converted data
|
|
167
|
+
output_file = output_directory / f"{sarif_file.stem}-converted.json"
|
|
168
|
+
logger.debug("Saving converted data to: %s", output_file)
|
|
169
|
+
|
|
170
|
+
save_data_to(
|
|
171
|
+
file=output_file,
|
|
172
|
+
data=converted_data, # Only log save details in single file mode
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
created_count, updated_count = self._map_sarif_to_integration_findings(converted_data)
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
"file": sarif_file.absolute(),
|
|
179
|
+
"output": output_file.absolute(),
|
|
180
|
+
"status": "success",
|
|
181
|
+
"error": None,
|
|
182
|
+
"created": created_count,
|
|
183
|
+
"updated": updated_count,
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
except Exception as e:
|
|
187
|
+
return {"file": sarif_file.absolute(), "output": None, "status": "failed", "error": str(e)}
|
|
188
|
+
|
|
189
|
+
def process_sarif_files(
|
|
190
|
+
self,
|
|
191
|
+
) -> None:
|
|
192
|
+
"""
|
|
193
|
+
Unified function to process SARIF files (single or multiple) and convert them using an API converter
|
|
194
|
+
|
|
195
|
+
:rtype: None
|
|
196
|
+
"""
|
|
197
|
+
if self.file_path.is_file():
|
|
198
|
+
sarif_files = [self.file_path]
|
|
199
|
+
else:
|
|
200
|
+
sarif_files = list(self.file_path.glob("*.sarif"))
|
|
201
|
+
|
|
202
|
+
# Ensure output directory exists
|
|
203
|
+
output_directory = self.file_path.parent / "converted"
|
|
204
|
+
check_file_path(output_directory)
|
|
205
|
+
|
|
206
|
+
# Initialize processing statistics
|
|
207
|
+
successful_conversions = 0
|
|
208
|
+
failed_conversions = 0
|
|
209
|
+
processed_files = []
|
|
210
|
+
|
|
211
|
+
if total_files := len(sarif_files):
|
|
212
|
+
self.create_scan_history()
|
|
213
|
+
else:
|
|
214
|
+
error_and_exit(f"No SARIF files found in directory: {self.file_path}")
|
|
215
|
+
if batch_mode := total_files > 1:
|
|
216
|
+
logger.info("Found %d SARIF files to convert", total_files)
|
|
217
|
+
|
|
218
|
+
# Process each SARIF file
|
|
219
|
+
for idx, sarif_file in enumerate(sarif_files, 1):
|
|
220
|
+
# Log progress for batch mode
|
|
221
|
+
if batch_mode:
|
|
222
|
+
logger.info("Processing file %d/%d: %s", idx, total_files, sarif_file.name)
|
|
223
|
+
else:
|
|
224
|
+
logger.info("Loading SARIF file: %s", sarif_file)
|
|
225
|
+
|
|
226
|
+
# Process single file
|
|
227
|
+
result = self._process_single_sarif_file(sarif_file, output_directory)
|
|
228
|
+
processed_files.append(result)
|
|
229
|
+
|
|
230
|
+
successful_conversions, failed_conversions = self._log_result_summary(
|
|
231
|
+
result=result,
|
|
232
|
+
successful_conversions=successful_conversions,
|
|
233
|
+
failed_conversions=failed_conversions,
|
|
234
|
+
batch_mode=batch_mode,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
self.scan_history.save()
|
|
238
|
+
if batch_mode:
|
|
239
|
+
logger.info(
|
|
240
|
+
"Batch conversion completed: %d successful, %d failed", successful_conversions, failed_conversions
|
|
241
|
+
)
|
|
242
|
+
logger.info("Converted files saved to: %s", output_directory.absolute())
|
|
243
|
+
else:
|
|
244
|
+
if successful_conversions == 1:
|
|
245
|
+
logger.info(
|
|
246
|
+
"Conversion completed successfully. Converted file saved to: %s", output_directory.absolute()
|
|
247
|
+
)
|
|
248
|
+
else:
|
|
249
|
+
error_and_exit("SARIF file conversion failed.")
|
|
250
|
+
|
|
251
|
+
def create_scan_history(self) -> None:
|
|
252
|
+
"""
|
|
253
|
+
Create a ScanHistory record for the SARIF import
|
|
254
|
+
|
|
255
|
+
:rtype: None
|
|
256
|
+
"""
|
|
257
|
+
if getattr(self.asset, "parentModule") == SecurityPlan.get_module_string():
|
|
258
|
+
parent_id = self.asset.parentId
|
|
259
|
+
parent_module: str = SecurityPlan.get_module_string()
|
|
260
|
+
else:
|
|
261
|
+
parent_id = self.asset.id
|
|
262
|
+
parent_module = self.asset.parentModule
|
|
263
|
+
self.scan_history = ScanHistory(
|
|
264
|
+
parentId=parent_id,
|
|
265
|
+
parentModule=parent_module,
|
|
266
|
+
scanningTool="Sarif Scanner",
|
|
267
|
+
scanDate=self.scan_date,
|
|
268
|
+
tenantsId=Asset.get_tenant_id(),
|
|
269
|
+
vLow=0,
|
|
270
|
+
vMedium=0,
|
|
271
|
+
vHigh=0,
|
|
272
|
+
vCritical=0,
|
|
273
|
+
).create()
|
|
274
|
+
|
|
275
|
+
@staticmethod
|
|
276
|
+
def _log_result_summary(
|
|
277
|
+
result: Dict[str, Any],
|
|
278
|
+
successful_conversions: int,
|
|
279
|
+
failed_conversions: int,
|
|
280
|
+
batch_mode: bool,
|
|
281
|
+
) -> tuple[int, int]:
|
|
282
|
+
"""
|
|
283
|
+
Log the result of a single SARIF file conversion and update statistics
|
|
284
|
+
|
|
285
|
+
:param Dict[str, Any] result: The result of the conversion
|
|
286
|
+
:param int successful_conversions: The number of successful conversions
|
|
287
|
+
:param int failed_conversions: The number of failed conversions
|
|
288
|
+
:param bool batch_mode: Whether running in batch mode (multiple files)
|
|
289
|
+
:return: Updated counts of successful and failed conversions
|
|
290
|
+
:rtype: tuple[int, int]
|
|
291
|
+
"""
|
|
292
|
+
if result["status"] == "success":
|
|
293
|
+
successful_conversions += 1
|
|
294
|
+
logger.debug("Successfully converted: %s -> %s", result["file"], result["output"])
|
|
295
|
+
logger.info(
|
|
296
|
+
"Successfully converted: %s -> %s, %s created vulnerabilities and %s updated vulnerabilities",
|
|
297
|
+
result["file"],
|
|
298
|
+
result["output"],
|
|
299
|
+
result["created"],
|
|
300
|
+
result["updated"],
|
|
301
|
+
)
|
|
302
|
+
else:
|
|
303
|
+
failed_conversions += 1
|
|
304
|
+
if batch_mode:
|
|
305
|
+
logger.error("Failed to convert %s: %s", result["file"], result["error"])
|
|
306
|
+
else:
|
|
307
|
+
error_and_exit(f"Failed to convert SARIF file: {result['error']}")
|
|
308
|
+
return successful_conversions, failed_conversions
|
|
309
|
+
|
|
310
|
+
def _map_sarif_to_integration_findings(
|
|
311
|
+
self,
|
|
312
|
+
converted_data: list[dict[str, Any]],
|
|
313
|
+
) -> tuple[int, int]:
|
|
314
|
+
"""
|
|
315
|
+
Map converted SARIF data to a list of IntegrationFinding objects
|
|
316
|
+
|
|
317
|
+
:param Any converted_data: The converted SARIF data from the API
|
|
318
|
+
:return: Number of created and updated vulnerabilities
|
|
319
|
+
:rtype: tuple[int, int]
|
|
320
|
+
"""
|
|
321
|
+
create_vuln_count = 0
|
|
322
|
+
updated_vuln_count = 0
|
|
323
|
+
# Convert the nested dictionary to a list of ApplicationSecurityPostureFinding objects for easier parsing
|
|
324
|
+
with self.progress as progress:
|
|
325
|
+
task = progress.add_task("Mapping Sarif data to RegScale vulnerabilities...", total=len(converted_data))
|
|
326
|
+
vulneribility_task = progress.add_task("Creating vulnerabilities and mappings...", total=0)
|
|
327
|
+
for result in converted_data:
|
|
328
|
+
name = self._parse_name(result)
|
|
329
|
+
findings = ApplicationSecurityPostureFinding(**result).vulnerabilities
|
|
330
|
+
progress.update(vulneribility_task, total=len(findings))
|
|
331
|
+
for finding in findings:
|
|
332
|
+
created, vuln = Vulnerability(
|
|
333
|
+
title=finding.title,
|
|
334
|
+
severity=Issue.assign_severity(finding.severity), # type: ignore
|
|
335
|
+
description=self._build_description(finding),
|
|
336
|
+
status=IssueStatus.Open,
|
|
337
|
+
firstSeen=self.scan_date,
|
|
338
|
+
lastSeen=self.scan_date,
|
|
339
|
+
plugInName=name,
|
|
340
|
+
plugInId=self._parse_plugin_id(finding),
|
|
341
|
+
parentId=self.parent_id,
|
|
342
|
+
parentModule=self.parent_module,
|
|
343
|
+
).create_or_update_with_status()
|
|
344
|
+
self.update_scan_history_count(vuln.severity)
|
|
345
|
+
if created:
|
|
346
|
+
create_vuln_count += 1
|
|
347
|
+
else:
|
|
348
|
+
updated_vuln_count += 1
|
|
349
|
+
# Map vulnerability to asset
|
|
350
|
+
VulnerabilityMapping(
|
|
351
|
+
assetId=self.asset.id,
|
|
352
|
+
vulnerabilityId=vuln.id,
|
|
353
|
+
scanId=self.scan_history.id,
|
|
354
|
+
securityPlanId=self.asset.parentId if self.asset.parentModule == "securityplans" else None,
|
|
355
|
+
status=vuln.status,
|
|
356
|
+
firstSeen=vuln.firstSeen,
|
|
357
|
+
lastSeen=vuln.lastSeen,
|
|
358
|
+
dateCreated=self.scan_date,
|
|
359
|
+
).create_or_update()
|
|
360
|
+
progress.update(vulneribility_task, advance=1)
|
|
361
|
+
progress.update(task, advance=1)
|
|
362
|
+
return create_vuln_count, updated_vuln_count
|
|
363
|
+
|
|
364
|
+
@staticmethod
|
|
365
|
+
def _parse_name(result: Dict[str, Any]) -> str:
|
|
366
|
+
"""
|
|
367
|
+
Parse the name and version from the result metadata
|
|
368
|
+
|
|
369
|
+
:param Dict[str, Any] result: The result to parse the name and version from
|
|
370
|
+
:return: The name and version string
|
|
371
|
+
:rtype: str
|
|
372
|
+
"""
|
|
373
|
+
if metadata := result["metadata"]:
|
|
374
|
+
name = metadata.get("product", {}).get("name", "Static Scanner")
|
|
375
|
+
version = metadata.get("version")
|
|
376
|
+
else:
|
|
377
|
+
name = "Static Scanner"
|
|
378
|
+
version = None
|
|
379
|
+
name = f"{name} {version}" if version else name
|
|
380
|
+
return name
|
|
381
|
+
|
|
382
|
+
@staticmethod
|
|
383
|
+
def _parse_plugin_id(finding: OcsfVuln) -> str:
|
|
384
|
+
"""
|
|
385
|
+
Parse the plugin ID from the finding
|
|
386
|
+
|
|
387
|
+
:param ocsf_vuln finding: The finding to parse the plugin ID from
|
|
388
|
+
:return: The plugin ID string
|
|
389
|
+
:rtype: str
|
|
390
|
+
"""
|
|
391
|
+
if cwe := getattr(finding, "cwe"):
|
|
392
|
+
return cwe.uid
|
|
393
|
+
elif cve := getattr(finding, "cve"):
|
|
394
|
+
return cve.uid
|
|
395
|
+
return finding.title
|
|
396
|
+
|
|
397
|
+
def update_scan_history_count(self, severity: str) -> None:
|
|
398
|
+
"""
|
|
399
|
+
Update the scan history count for a given severity
|
|
400
|
+
|
|
401
|
+
:param str severity: The severity of the vulnerability
|
|
402
|
+
:rtype: None
|
|
403
|
+
"""
|
|
404
|
+
if "Low" in severity:
|
|
405
|
+
self.scan_history.vLow += 1
|
|
406
|
+
elif "Moderate" in severity:
|
|
407
|
+
self.scan_history.vMedium += 1
|
|
408
|
+
elif "High" in severity:
|
|
409
|
+
self.scan_history.vHigh += 1
|
|
410
|
+
elif "Critical" in severity:
|
|
411
|
+
self.scan_history.vCritical += 1
|
|
412
|
+
else:
|
|
413
|
+
self.scan_history.vInfo += 1
|
|
414
|
+
|
|
415
|
+
@staticmethod
|
|
416
|
+
def _build_description(finding: OcsfVuln) -> str:
|
|
417
|
+
"""
|
|
418
|
+
Build a detailed description for a vulnerability finding
|
|
419
|
+
|
|
420
|
+
:param ocsf_vuln finding: The finding to build the description for
|
|
421
|
+
:return: Detailed description string
|
|
422
|
+
:rtype: str
|
|
423
|
+
"""
|
|
424
|
+
description = f"{finding.desc}\n"
|
|
425
|
+
for affected_code in getattr(finding, "affected_code", []):
|
|
426
|
+
|
|
427
|
+
for key, value in affected_code.dict().items():
|
|
428
|
+
if isinstance(value, dict):
|
|
429
|
+
for sub_key, sub_value in value.items():
|
|
430
|
+
description += f"file {sub_key}: {sub_value}\n"
|
|
431
|
+
description += f"{key}: {value}\n"
|
|
432
|
+
return description
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""SARIF Converter integration for RegScale CLI"""
|
|
4
|
+
|
|
5
|
+
import datetime
|
|
6
|
+
import logging
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from regscale.core.app.utils.app_utils import (
|
|
13
|
+
get_current_datetime,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger("regscale")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@click.group()
|
|
20
|
+
def sarif():
|
|
21
|
+
"""Convert SARIF files to OCSF data using an API converter."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@sarif.command(name="import")
|
|
25
|
+
@click.option(
|
|
26
|
+
"--file_path",
|
|
27
|
+
"-f",
|
|
28
|
+
type=click.Path(exists=True, file_okay=True, dir_okay=True, path_type=Path),
|
|
29
|
+
help="Path to the SARIF file or a directory of files to convert",
|
|
30
|
+
prompt="Enter the path",
|
|
31
|
+
required=True,
|
|
32
|
+
)
|
|
33
|
+
@click.option(
|
|
34
|
+
"--asset_id",
|
|
35
|
+
"-id",
|
|
36
|
+
type=click.INT,
|
|
37
|
+
help="The RegScale Asset ID # to import the findings to.",
|
|
38
|
+
prompt="RegScale Asset ID",
|
|
39
|
+
required=True,
|
|
40
|
+
)
|
|
41
|
+
@click.option(
|
|
42
|
+
"--scan_date",
|
|
43
|
+
"-sd",
|
|
44
|
+
type=click.DateTime(formats=["%Y-%m-%d"]),
|
|
45
|
+
help="The scan date of the file.",
|
|
46
|
+
required=False,
|
|
47
|
+
default=get_current_datetime("%Y-%m-%d"),
|
|
48
|
+
)
|
|
49
|
+
def import_sarif(file_path: Path, asset_id: int, scan_date: Optional[datetime.datetime] = None) -> None:
|
|
50
|
+
"""Convert a SARIF file(s) to OCSF format using an API converter."""
|
|
51
|
+
process_sarif_files(file_path, asset_id, scan_date)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def process_sarif_files(file_path: Path, asset_id: int, scan_date: Optional[datetime.datetime]) -> None:
|
|
55
|
+
"""
|
|
56
|
+
Process SARIF files for import.
|
|
57
|
+
|
|
58
|
+
:param Path file_path: Path to the SARIF file or directory of files
|
|
59
|
+
:param int asset_id: The RegScale Asset ID to import the findings to
|
|
60
|
+
:param Optional[datetime.datetime] scan_date: The scan date of the file
|
|
61
|
+
:return: None
|
|
62
|
+
"""
|
|
63
|
+
from regscale.integrations.commercial.sarif.sairf_importer import SarifImporter
|
|
64
|
+
|
|
65
|
+
if not scan_date:
|
|
66
|
+
scan_date = get_current_datetime()
|
|
67
|
+
SarifImporter(file_path, asset_id, scan_date=scan_date)
|