regscale-cli 6.16.0.0__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.
Potentially problematic release.
This version of regscale-cli might be problematic. Click here for more details.
- regscale/__init__.py +1 -0
- regscale/airflow/__init__.py +9 -0
- regscale/airflow/azure/__init__.py +9 -0
- regscale/airflow/azure/cli.py +89 -0
- regscale/airflow/azure/upload_dags.py +116 -0
- regscale/airflow/click_dags.py +127 -0
- regscale/airflow/click_mixins.py +82 -0
- regscale/airflow/config.py +25 -0
- regscale/airflow/factories/__init__.py +0 -0
- regscale/airflow/factories/connections.py +58 -0
- regscale/airflow/factories/workflows.py +78 -0
- regscale/airflow/hierarchy.py +88 -0
- regscale/airflow/operators/__init__.py +0 -0
- regscale/airflow/operators/click.py +36 -0
- regscale/airflow/sensors/__init__.py +0 -0
- regscale/airflow/sensors/sql.py +107 -0
- regscale/airflow/sessions/__init__.py +0 -0
- regscale/airflow/sessions/sql/__init__.py +3 -0
- regscale/airflow/sessions/sql/queries.py +64 -0
- regscale/airflow/sessions/sql/sql_server_queries.py +248 -0
- regscale/airflow/tasks/__init__.py +0 -0
- regscale/airflow/tasks/branches.py +22 -0
- regscale/airflow/tasks/cli.py +116 -0
- regscale/airflow/tasks/click.py +73 -0
- regscale/airflow/tasks/debugging.py +9 -0
- regscale/airflow/tasks/groups.py +116 -0
- regscale/airflow/tasks/init.py +60 -0
- regscale/airflow/tasks/states.py +47 -0
- regscale/airflow/tasks/workflows.py +36 -0
- regscale/ansible/__init__.py +9 -0
- regscale/core/__init__.py +0 -0
- regscale/core/app/__init__.py +3 -0
- regscale/core/app/api.py +571 -0
- regscale/core/app/application.py +665 -0
- regscale/core/app/internal/__init__.py +136 -0
- regscale/core/app/internal/admin_actions.py +230 -0
- regscale/core/app/internal/assessments_editor.py +873 -0
- regscale/core/app/internal/catalog.py +316 -0
- regscale/core/app/internal/comparison.py +459 -0
- regscale/core/app/internal/control_editor.py +571 -0
- regscale/core/app/internal/encrypt.py +79 -0
- regscale/core/app/internal/evidence.py +1240 -0
- regscale/core/app/internal/file_uploads.py +151 -0
- regscale/core/app/internal/healthcheck.py +66 -0
- regscale/core/app/internal/login.py +305 -0
- regscale/core/app/internal/migrations.py +240 -0
- regscale/core/app/internal/model_editor.py +1701 -0
- regscale/core/app/internal/poam_editor.py +632 -0
- regscale/core/app/internal/workflow.py +105 -0
- regscale/core/app/logz.py +74 -0
- regscale/core/app/utils/XMLIR.py +258 -0
- regscale/core/app/utils/__init__.py +0 -0
- regscale/core/app/utils/api_handler.py +358 -0
- regscale/core/app/utils/app_utils.py +1110 -0
- regscale/core/app/utils/catalog_utils/__init__.py +0 -0
- regscale/core/app/utils/catalog_utils/common.py +91 -0
- regscale/core/app/utils/catalog_utils/compare_catalog.py +193 -0
- regscale/core/app/utils/catalog_utils/diagnostic_catalog.py +97 -0
- regscale/core/app/utils/catalog_utils/download_catalog.py +103 -0
- regscale/core/app/utils/catalog_utils/update_catalog.py +718 -0
- regscale/core/app/utils/catalog_utils/update_catalog_v2.py +1378 -0
- regscale/core/app/utils/catalog_utils/update_catalog_v3.py +1272 -0
- regscale/core/app/utils/catalog_utils/update_plans.py +334 -0
- regscale/core/app/utils/file_utils.py +238 -0
- regscale/core/app/utils/parser_utils.py +81 -0
- regscale/core/app/utils/pickle_file_handler.py +57 -0
- regscale/core/app/utils/regscale_utils.py +319 -0
- regscale/core/app/utils/report_utils.py +119 -0
- regscale/core/app/utils/variables.py +226 -0
- regscale/core/decorators.py +31 -0
- regscale/core/lazy_group.py +65 -0
- regscale/core/login.py +63 -0
- regscale/core/server/__init__.py +0 -0
- regscale/core/server/flask_api.py +473 -0
- regscale/core/server/helpers.py +373 -0
- regscale/core/server/rest.py +64 -0
- regscale/core/server/static/css/bootstrap.css +6030 -0
- regscale/core/server/static/css/bootstrap.min.css +6 -0
- regscale/core/server/static/css/main.css +176 -0
- regscale/core/server/static/images/regscale-cli.svg +49 -0
- regscale/core/server/static/images/regscale.svg +38 -0
- regscale/core/server/templates/base.html +74 -0
- regscale/core/server/templates/index.html +43 -0
- regscale/core/server/templates/login.html +28 -0
- regscale/core/server/templates/make_base64.html +22 -0
- regscale/core/server/templates/upload_STIG.html +109 -0
- regscale/core/server/templates/upload_STIG_result.html +26 -0
- regscale/core/server/templates/upload_ssp.html +144 -0
- regscale/core/server/templates/upload_ssp_result.html +128 -0
- regscale/core/static/__init__.py +0 -0
- regscale/core/static/regex.py +14 -0
- regscale/core/utils/__init__.py +117 -0
- regscale/core/utils/click_utils.py +13 -0
- regscale/core/utils/date.py +238 -0
- regscale/core/utils/graphql.py +254 -0
- regscale/core/utils/urls.py +23 -0
- regscale/dev/__init__.py +6 -0
- regscale/dev/analysis.py +454 -0
- regscale/dev/cli.py +235 -0
- regscale/dev/code_gen.py +492 -0
- regscale/dev/dirs.py +69 -0
- regscale/dev/docs.py +384 -0
- regscale/dev/monitoring.py +26 -0
- regscale/dev/profiling.py +216 -0
- regscale/exceptions/__init__.py +4 -0
- regscale/exceptions/license_exception.py +7 -0
- regscale/exceptions/validation_exception.py +9 -0
- regscale/integrations/__init__.py +1 -0
- regscale/integrations/commercial/__init__.py +486 -0
- regscale/integrations/commercial/ad.py +433 -0
- regscale/integrations/commercial/amazon/__init__.py +0 -0
- regscale/integrations/commercial/amazon/common.py +106 -0
- regscale/integrations/commercial/aqua/__init__.py +0 -0
- regscale/integrations/commercial/aqua/aqua.py +91 -0
- regscale/integrations/commercial/aws/__init__.py +6 -0
- regscale/integrations/commercial/aws/cli.py +322 -0
- regscale/integrations/commercial/aws/inventory/__init__.py +110 -0
- regscale/integrations/commercial/aws/inventory/base.py +64 -0
- regscale/integrations/commercial/aws/inventory/resources/__init__.py +19 -0
- regscale/integrations/commercial/aws/inventory/resources/compute.py +234 -0
- regscale/integrations/commercial/aws/inventory/resources/containers.py +113 -0
- regscale/integrations/commercial/aws/inventory/resources/database.py +101 -0
- regscale/integrations/commercial/aws/inventory/resources/integration.py +237 -0
- regscale/integrations/commercial/aws/inventory/resources/networking.py +253 -0
- regscale/integrations/commercial/aws/inventory/resources/security.py +240 -0
- regscale/integrations/commercial/aws/inventory/resources/storage.py +91 -0
- regscale/integrations/commercial/aws/scanner.py +823 -0
- regscale/integrations/commercial/azure/__init__.py +0 -0
- regscale/integrations/commercial/azure/common.py +32 -0
- regscale/integrations/commercial/azure/intune.py +488 -0
- regscale/integrations/commercial/azure/scanner.py +49 -0
- regscale/integrations/commercial/burp.py +78 -0
- regscale/integrations/commercial/cpe.py +144 -0
- regscale/integrations/commercial/crowdstrike.py +1117 -0
- regscale/integrations/commercial/defender.py +1511 -0
- regscale/integrations/commercial/dependabot.py +210 -0
- regscale/integrations/commercial/durosuite/__init__.py +0 -0
- regscale/integrations/commercial/durosuite/api.py +1546 -0
- regscale/integrations/commercial/durosuite/process_devices.py +101 -0
- regscale/integrations/commercial/durosuite/scanner.py +637 -0
- regscale/integrations/commercial/durosuite/variables.py +21 -0
- regscale/integrations/commercial/ecr.py +90 -0
- regscale/integrations/commercial/gcp/__init__.py +237 -0
- regscale/integrations/commercial/gcp/auth.py +96 -0
- regscale/integrations/commercial/gcp/control_tests.py +238 -0
- regscale/integrations/commercial/gcp/variables.py +18 -0
- regscale/integrations/commercial/gitlab.py +332 -0
- regscale/integrations/commercial/grype.py +165 -0
- regscale/integrations/commercial/ibm.py +90 -0
- regscale/integrations/commercial/import_all/__init__.py +0 -0
- regscale/integrations/commercial/import_all/import_all_cmd.py +467 -0
- regscale/integrations/commercial/import_all/scan_file_fingerprints.json +27 -0
- regscale/integrations/commercial/jira.py +1046 -0
- regscale/integrations/commercial/mappings/__init__.py +0 -0
- regscale/integrations/commercial/mappings/csf_controls.json +713 -0
- regscale/integrations/commercial/mappings/nist_800_53_r5_controls.json +1516 -0
- regscale/integrations/commercial/nessus/__init__.py +0 -0
- regscale/integrations/commercial/nessus/nessus_utils.py +429 -0
- regscale/integrations/commercial/nessus/scanner.py +416 -0
- regscale/integrations/commercial/nexpose.py +90 -0
- regscale/integrations/commercial/okta.py +798 -0
- regscale/integrations/commercial/opentext/__init__.py +0 -0
- regscale/integrations/commercial/opentext/click.py +99 -0
- regscale/integrations/commercial/opentext/scanner.py +143 -0
- regscale/integrations/commercial/prisma.py +91 -0
- regscale/integrations/commercial/qualys.py +1462 -0
- regscale/integrations/commercial/salesforce.py +980 -0
- regscale/integrations/commercial/sap/__init__.py +0 -0
- regscale/integrations/commercial/sap/click.py +31 -0
- regscale/integrations/commercial/sap/sysdig/__init__.py +0 -0
- regscale/integrations/commercial/sap/sysdig/click.py +57 -0
- regscale/integrations/commercial/sap/sysdig/sysdig_scanner.py +190 -0
- regscale/integrations/commercial/sap/tenable/__init__.py +0 -0
- regscale/integrations/commercial/sap/tenable/click.py +49 -0
- regscale/integrations/commercial/sap/tenable/scanner.py +196 -0
- regscale/integrations/commercial/servicenow.py +1756 -0
- regscale/integrations/commercial/sicura/__init__.py +0 -0
- regscale/integrations/commercial/sicura/api.py +855 -0
- regscale/integrations/commercial/sicura/commands.py +73 -0
- regscale/integrations/commercial/sicura/scanner.py +481 -0
- regscale/integrations/commercial/sicura/variables.py +16 -0
- regscale/integrations/commercial/snyk.py +90 -0
- regscale/integrations/commercial/sonarcloud.py +260 -0
- regscale/integrations/commercial/sqlserver.py +369 -0
- regscale/integrations/commercial/stig_mapper_integration/__init__.py +0 -0
- regscale/integrations/commercial/stig_mapper_integration/click_commands.py +38 -0
- regscale/integrations/commercial/stig_mapper_integration/mapping_engine.py +353 -0
- regscale/integrations/commercial/stigv2/__init__.py +0 -0
- regscale/integrations/commercial/stigv2/ckl_parser.py +349 -0
- regscale/integrations/commercial/stigv2/click_commands.py +95 -0
- regscale/integrations/commercial/stigv2/stig_integration.py +202 -0
- regscale/integrations/commercial/synqly/__init__.py +0 -0
- regscale/integrations/commercial/synqly/assets.py +46 -0
- regscale/integrations/commercial/synqly/ticketing.py +132 -0
- regscale/integrations/commercial/synqly/vulnerabilities.py +223 -0
- regscale/integrations/commercial/synqly_jira.py +840 -0
- regscale/integrations/commercial/tenablev2/__init__.py +0 -0
- regscale/integrations/commercial/tenablev2/authenticate.py +31 -0
- regscale/integrations/commercial/tenablev2/click.py +1584 -0
- regscale/integrations/commercial/tenablev2/scanner.py +504 -0
- regscale/integrations/commercial/tenablev2/stig_parsers.py +140 -0
- regscale/integrations/commercial/tenablev2/utils.py +78 -0
- regscale/integrations/commercial/tenablev2/variables.py +17 -0
- regscale/integrations/commercial/trivy.py +162 -0
- regscale/integrations/commercial/veracode.py +96 -0
- regscale/integrations/commercial/wizv2/WizDataMixin.py +97 -0
- regscale/integrations/commercial/wizv2/__init__.py +0 -0
- regscale/integrations/commercial/wizv2/click.py +429 -0
- regscale/integrations/commercial/wizv2/constants.py +1001 -0
- regscale/integrations/commercial/wizv2/issue.py +361 -0
- regscale/integrations/commercial/wizv2/models.py +112 -0
- regscale/integrations/commercial/wizv2/parsers.py +339 -0
- regscale/integrations/commercial/wizv2/sbom.py +115 -0
- regscale/integrations/commercial/wizv2/scanner.py +416 -0
- regscale/integrations/commercial/wizv2/utils.py +796 -0
- regscale/integrations/commercial/wizv2/variables.py +39 -0
- regscale/integrations/commercial/wizv2/wiz_auth.py +159 -0
- regscale/integrations/commercial/xray.py +91 -0
- regscale/integrations/integration/__init__.py +2 -0
- regscale/integrations/integration/integration.py +26 -0
- regscale/integrations/integration/inventory.py +17 -0
- regscale/integrations/integration/issue.py +100 -0
- regscale/integrations/integration_override.py +149 -0
- regscale/integrations/public/__init__.py +103 -0
- regscale/integrations/public/cisa.py +641 -0
- regscale/integrations/public/criticality_updater.py +70 -0
- regscale/integrations/public/emass.py +411 -0
- regscale/integrations/public/emass_slcm_import.py +697 -0
- regscale/integrations/public/fedramp/__init__.py +0 -0
- regscale/integrations/public/fedramp/appendix_parser.py +548 -0
- regscale/integrations/public/fedramp/click.py +479 -0
- regscale/integrations/public/fedramp/components.py +714 -0
- regscale/integrations/public/fedramp/docx_parser.py +259 -0
- regscale/integrations/public/fedramp/fedramp_cis_crm.py +1124 -0
- regscale/integrations/public/fedramp/fedramp_common.py +3181 -0
- regscale/integrations/public/fedramp/fedramp_docx.py +388 -0
- regscale/integrations/public/fedramp/fedramp_five.py +2343 -0
- regscale/integrations/public/fedramp/fedramp_traversal.py +138 -0
- regscale/integrations/public/fedramp/import_fedramp_r4_ssp.py +279 -0
- regscale/integrations/public/fedramp/import_workbook.py +495 -0
- regscale/integrations/public/fedramp/inventory_items.py +244 -0
- regscale/integrations/public/fedramp/mappings/__init__.py +0 -0
- regscale/integrations/public/fedramp/mappings/fedramp_r4_parts.json +7388 -0
- regscale/integrations/public/fedramp/mappings/fedramp_r5_params.json +8636 -0
- regscale/integrations/public/fedramp/mappings/fedramp_r5_parts.json +9605 -0
- regscale/integrations/public/fedramp/mappings/system_roles.py +34 -0
- regscale/integrations/public/fedramp/mappings/user.py +175 -0
- regscale/integrations/public/fedramp/mappings/values.py +141 -0
- regscale/integrations/public/fedramp/markdown_parser.py +150 -0
- regscale/integrations/public/fedramp/metadata.py +689 -0
- regscale/integrations/public/fedramp/models/__init__.py +59 -0
- regscale/integrations/public/fedramp/models/leveraged_auth_new.py +168 -0
- regscale/integrations/public/fedramp/models/poam_importer.py +522 -0
- regscale/integrations/public/fedramp/parts_mapper.py +107 -0
- regscale/integrations/public/fedramp/poam/__init__.py +0 -0
- regscale/integrations/public/fedramp/poam/scanner.py +851 -0
- regscale/integrations/public/fedramp/properties.py +201 -0
- regscale/integrations/public/fedramp/reporting.py +84 -0
- regscale/integrations/public/fedramp/resources.py +496 -0
- regscale/integrations/public/fedramp/rosetta.py +110 -0
- regscale/integrations/public/fedramp/ssp_logger.py +87 -0
- regscale/integrations/public/fedramp/system_characteristics.py +922 -0
- regscale/integrations/public/fedramp/system_control_implementations.py +582 -0
- regscale/integrations/public/fedramp/system_implementation.py +190 -0
- regscale/integrations/public/fedramp/xml_utils.py +87 -0
- regscale/integrations/public/nist_catalog.py +275 -0
- regscale/integrations/public/oscal.py +1946 -0
- regscale/integrations/public/otx.py +169 -0
- regscale/integrations/scanner_integration.py +2692 -0
- regscale/integrations/variables.py +25 -0
- regscale/models/__init__.py +7 -0
- regscale/models/app_models/__init__.py +5 -0
- regscale/models/app_models/catalog_compare.py +213 -0
- regscale/models/app_models/click.py +252 -0
- regscale/models/app_models/datetime_encoder.py +21 -0
- regscale/models/app_models/import_validater.py +321 -0
- regscale/models/app_models/mapping.py +260 -0
- regscale/models/app_models/pipeline.py +37 -0
- regscale/models/click_models.py +413 -0
- regscale/models/config.py +154 -0
- regscale/models/email_style.css +67 -0
- regscale/models/hierarchy.py +8 -0
- regscale/models/inspect_models.py +79 -0
- regscale/models/integration_models/__init__.py +0 -0
- regscale/models/integration_models/amazon_models/__init__.py +0 -0
- regscale/models/integration_models/amazon_models/inspector.py +262 -0
- regscale/models/integration_models/amazon_models/inspector_scan.py +206 -0
- regscale/models/integration_models/aqua.py +247 -0
- regscale/models/integration_models/azure_alerts.py +255 -0
- regscale/models/integration_models/base64.py +23 -0
- regscale/models/integration_models/burp.py +433 -0
- regscale/models/integration_models/burp_models.py +128 -0
- regscale/models/integration_models/cisa_kev_data.json +19333 -0
- regscale/models/integration_models/defender_data.py +93 -0
- regscale/models/integration_models/defenderimport.py +143 -0
- regscale/models/integration_models/drf.py +443 -0
- regscale/models/integration_models/ecr_models/__init__.py +0 -0
- regscale/models/integration_models/ecr_models/data.py +69 -0
- regscale/models/integration_models/ecr_models/ecr.py +239 -0
- regscale/models/integration_models/flat_file_importer.py +1079 -0
- regscale/models/integration_models/grype_import.py +247 -0
- regscale/models/integration_models/ibm.py +126 -0
- regscale/models/integration_models/implementation_results.py +85 -0
- regscale/models/integration_models/nexpose.py +140 -0
- regscale/models/integration_models/prisma.py +202 -0
- regscale/models/integration_models/qualys.py +720 -0
- regscale/models/integration_models/qualys_scanner.py +160 -0
- regscale/models/integration_models/sbom/__init__.py +0 -0
- regscale/models/integration_models/sbom/cyclone_dx.py +139 -0
- regscale/models/integration_models/send_reminders.py +620 -0
- regscale/models/integration_models/snyk.py +155 -0
- regscale/models/integration_models/synqly_models/__init__.py +0 -0
- regscale/models/integration_models/synqly_models/capabilities.json +1 -0
- regscale/models/integration_models/synqly_models/connector_types.py +22 -0
- regscale/models/integration_models/synqly_models/connectors/__init__.py +7 -0
- regscale/models/integration_models/synqly_models/connectors/assets.py +97 -0
- regscale/models/integration_models/synqly_models/connectors/ticketing.py +583 -0
- regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +169 -0
- regscale/models/integration_models/synqly_models/ocsf_mapper.py +331 -0
- regscale/models/integration_models/synqly_models/param.py +72 -0
- regscale/models/integration_models/synqly_models/synqly_model.py +733 -0
- regscale/models/integration_models/synqly_models/tenants.py +39 -0
- regscale/models/integration_models/tenable_models/__init__.py +0 -0
- regscale/models/integration_models/tenable_models/integration.py +187 -0
- regscale/models/integration_models/tenable_models/models.py +513 -0
- regscale/models/integration_models/trivy_import.py +231 -0
- regscale/models/integration_models/veracode.py +217 -0
- regscale/models/integration_models/xray.py +135 -0
- regscale/models/locking.py +100 -0
- regscale/models/platform.py +110 -0
- regscale/models/regscale_models/__init__.py +67 -0
- regscale/models/regscale_models/assessment.py +570 -0
- regscale/models/regscale_models/assessment_plan.py +52 -0
- regscale/models/regscale_models/asset.py +567 -0
- regscale/models/regscale_models/asset_mapping.py +190 -0
- regscale/models/regscale_models/case.py +42 -0
- regscale/models/regscale_models/catalog.py +261 -0
- regscale/models/regscale_models/cci.py +46 -0
- regscale/models/regscale_models/change.py +167 -0
- regscale/models/regscale_models/checklist.py +372 -0
- regscale/models/regscale_models/comment.py +49 -0
- regscale/models/regscale_models/compliance_settings.py +112 -0
- regscale/models/regscale_models/component.py +412 -0
- regscale/models/regscale_models/component_mapping.py +65 -0
- regscale/models/regscale_models/control.py +38 -0
- regscale/models/regscale_models/control_implementation.py +1128 -0
- regscale/models/regscale_models/control_objective.py +261 -0
- regscale/models/regscale_models/control_parameter.py +100 -0
- regscale/models/regscale_models/control_test.py +34 -0
- regscale/models/regscale_models/control_test_plan.py +75 -0
- regscale/models/regscale_models/control_test_result.py +52 -0
- regscale/models/regscale_models/custom_field.py +245 -0
- regscale/models/regscale_models/data.py +109 -0
- regscale/models/regscale_models/data_center.py +40 -0
- regscale/models/regscale_models/deviation.py +203 -0
- regscale/models/regscale_models/email.py +97 -0
- regscale/models/regscale_models/evidence.py +47 -0
- regscale/models/regscale_models/evidence_mapping.py +40 -0
- regscale/models/regscale_models/facility.py +59 -0
- regscale/models/regscale_models/file.py +382 -0
- regscale/models/regscale_models/filetag.py +37 -0
- regscale/models/regscale_models/form_field_value.py +94 -0
- regscale/models/regscale_models/group.py +169 -0
- regscale/models/regscale_models/implementation_objective.py +335 -0
- regscale/models/regscale_models/implementation_option.py +275 -0
- regscale/models/regscale_models/implementation_role.py +33 -0
- regscale/models/regscale_models/incident.py +177 -0
- regscale/models/regscale_models/interconnection.py +43 -0
- regscale/models/regscale_models/issue.py +1176 -0
- regscale/models/regscale_models/leveraged_authorization.py +125 -0
- regscale/models/regscale_models/line_of_inquiry.py +52 -0
- regscale/models/regscale_models/link.py +205 -0
- regscale/models/regscale_models/meta_data.py +64 -0
- regscale/models/regscale_models/mixins/__init__.py +0 -0
- regscale/models/regscale_models/mixins/parent_cache.py +124 -0
- regscale/models/regscale_models/module.py +224 -0
- regscale/models/regscale_models/modules.py +191 -0
- regscale/models/regscale_models/objective.py +14 -0
- regscale/models/regscale_models/parameter.py +87 -0
- regscale/models/regscale_models/ports_protocol.py +81 -0
- regscale/models/regscale_models/privacy.py +89 -0
- regscale/models/regscale_models/profile.py +50 -0
- regscale/models/regscale_models/profile_link.py +68 -0
- regscale/models/regscale_models/profile_mapping.py +124 -0
- regscale/models/regscale_models/project.py +63 -0
- regscale/models/regscale_models/property.py +278 -0
- regscale/models/regscale_models/question.py +85 -0
- regscale/models/regscale_models/questionnaire.py +87 -0
- regscale/models/regscale_models/questionnaire_instance.py +177 -0
- regscale/models/regscale_models/rbac.py +132 -0
- regscale/models/regscale_models/reference.py +86 -0
- regscale/models/regscale_models/regscale_model.py +1643 -0
- regscale/models/regscale_models/requirement.py +29 -0
- regscale/models/regscale_models/risk.py +274 -0
- regscale/models/regscale_models/sbom.py +54 -0
- regscale/models/regscale_models/scan_history.py +436 -0
- regscale/models/regscale_models/search.py +53 -0
- regscale/models/regscale_models/security_control.py +132 -0
- regscale/models/regscale_models/security_plan.py +204 -0
- regscale/models/regscale_models/software_inventory.py +159 -0
- regscale/models/regscale_models/stake_holder.py +64 -0
- regscale/models/regscale_models/stig.py +647 -0
- regscale/models/regscale_models/supply_chain.py +152 -0
- regscale/models/regscale_models/system_role.py +188 -0
- regscale/models/regscale_models/system_role_external_assignment.py +40 -0
- regscale/models/regscale_models/tag.py +37 -0
- regscale/models/regscale_models/tag_mapping.py +19 -0
- regscale/models/regscale_models/task.py +133 -0
- regscale/models/regscale_models/threat.py +196 -0
- regscale/models/regscale_models/user.py +175 -0
- regscale/models/regscale_models/user_group.py +55 -0
- regscale/models/regscale_models/vulnerability.py +242 -0
- regscale/models/regscale_models/vulnerability_mapping.py +162 -0
- regscale/models/regscale_models/workflow.py +55 -0
- regscale/models/regscale_models/workflow_action.py +34 -0
- regscale/models/regscale_models/workflow_instance.py +269 -0
- regscale/models/regscale_models/workflow_instance_step.py +114 -0
- regscale/models/regscale_models/workflow_template.py +58 -0
- regscale/models/regscale_models/workflow_template_step.py +45 -0
- regscale/regscale.py +815 -0
- regscale/utils/__init__.py +7 -0
- regscale/utils/b64conversion.py +14 -0
- regscale/utils/click_utils.py +118 -0
- regscale/utils/decorators.py +48 -0
- regscale/utils/dict_utils.py +59 -0
- regscale/utils/files.py +79 -0
- regscale/utils/fxns.py +30 -0
- regscale/utils/graphql_client.py +113 -0
- regscale/utils/lists.py +16 -0
- regscale/utils/numbers.py +12 -0
- regscale/utils/shell.py +148 -0
- regscale/utils/string.py +121 -0
- regscale/utils/synqly_utils.py +165 -0
- regscale/utils/threading/__init__.py +8 -0
- regscale/utils/threading/threadhandler.py +131 -0
- regscale/utils/threading/threadsafe_counter.py +47 -0
- regscale/utils/threading/threadsafe_dict.py +242 -0
- regscale/utils/threading/threadsafe_list.py +83 -0
- regscale/utils/version.py +104 -0
- regscale/validation/__init__.py +0 -0
- regscale/validation/address.py +37 -0
- regscale/validation/record.py +48 -0
- regscale/visualization/__init__.py +5 -0
- regscale/visualization/click.py +34 -0
- regscale_cli-6.16.0.0.dist-info/LICENSE +21 -0
- regscale_cli-6.16.0.0.dist-info/METADATA +659 -0
- regscale_cli-6.16.0.0.dist-info/RECORD +481 -0
- regscale_cli-6.16.0.0.dist-info/WHEEL +5 -0
- regscale_cli-6.16.0.0.dist-info/entry_points.txt +6 -0
- regscale_cli-6.16.0.0.dist-info/top_level.txt +2 -0
- tests/fixtures/__init__.py +2 -0
- tests/fixtures/api.py +87 -0
- tests/fixtures/models.py +91 -0
- tests/fixtures/test_fixture.py +144 -0
- tests/mocks/__init__.py +0 -0
- tests/mocks/objects.py +3 -0
- tests/mocks/response.py +32 -0
- tests/mocks/xml.py +13 -0
- tests/regscale/__init__.py +0 -0
- tests/regscale/core/__init__.py +0 -0
- tests/regscale/core/test_api.py +232 -0
- tests/regscale/core/test_app.py +406 -0
- tests/regscale/core/test_login.py +37 -0
- tests/regscale/core/test_logz.py +66 -0
- tests/regscale/core/test_sbom_generator.py +87 -0
- tests/regscale/core/test_validation_utils.py +163 -0
- tests/regscale/core/test_version.py +78 -0
- tests/regscale/models/__init__.py +0 -0
- tests/regscale/models/test_asset.py +71 -0
- tests/regscale/models/test_config.py +26 -0
- tests/regscale/models/test_control_implementation.py +27 -0
- tests/regscale/models/test_import.py +97 -0
- tests/regscale/models/test_issue.py +36 -0
- tests/regscale/models/test_mapping.py +52 -0
- tests/regscale/models/test_platform.py +31 -0
- tests/regscale/models/test_regscale_model.py +346 -0
- tests/regscale/models/test_report.py +32 -0
- tests/regscale/models/test_tenable_integrations.py +118 -0
- tests/regscale/models/test_user_model.py +121 -0
- tests/regscale/test_about.py +19 -0
- tests/regscale/test_authorization.py +65 -0
|
@@ -0,0 +1,1117 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""CrowdStrike RegScale integration"""
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
from enum import Enum
|
|
8
|
+
from typing import Callable, Optional, Union, Dict
|
|
9
|
+
from urllib.parse import urljoin
|
|
10
|
+
|
|
11
|
+
import click
|
|
12
|
+
from falconpy import Incidents, Intel, OAuth2, UserManagement
|
|
13
|
+
from rich.console import Console
|
|
14
|
+
from rich.progress import track
|
|
15
|
+
from rich.table import Table
|
|
16
|
+
|
|
17
|
+
from regscale.core.app.api import Api
|
|
18
|
+
from regscale.core.app.application import Application
|
|
19
|
+
from regscale.core.app.logz import create_logger
|
|
20
|
+
from regscale.core.app.utils.app_utils import (
|
|
21
|
+
error_and_exit,
|
|
22
|
+
format_data_to_html,
|
|
23
|
+
get_current_datetime,
|
|
24
|
+
)
|
|
25
|
+
from regscale.core.app.utils.app_utils import remove_keys
|
|
26
|
+
from regscale.core.app.utils.regscale_utils import verify_provided_module
|
|
27
|
+
from regscale.models import regscale_id, regscale_module, regscale_models
|
|
28
|
+
from regscale.models.regscale_models import Catalog
|
|
29
|
+
from regscale.models.regscale_models import (
|
|
30
|
+
ControlImplementation,
|
|
31
|
+
ControlImplementationStatus,
|
|
32
|
+
)
|
|
33
|
+
from regscale.models.regscale_models.asset import Asset
|
|
34
|
+
from regscale.models.regscale_models.incident import Incident
|
|
35
|
+
|
|
36
|
+
#####################################################################################################
|
|
37
|
+
#
|
|
38
|
+
# CrowdStrike API Documentation: https://dash.readme.com/to/crowdstrike-enterprise?redirect=%2Fcrowdstrike%2Fdocs
|
|
39
|
+
|
|
40
|
+
# Sync incidents from CrowdStrike EDR into RegScale
|
|
41
|
+
|
|
42
|
+
# Allow customer to set severity level of what level of alerts they want to sync, set via init.yaml
|
|
43
|
+
|
|
44
|
+
# Check to make sure alert does not already exist, if it does, update with latest info, if it doesn't, create a new one
|
|
45
|
+
|
|
46
|
+
# Ensure you can link back to the alert in CrowdStrike
|
|
47
|
+
|
|
48
|
+
# Get with Jim Townsend on access to a DEV environment or customer sandbox
|
|
49
|
+
|
|
50
|
+
#####################################################################################################
|
|
51
|
+
|
|
52
|
+
logger = create_logger()
|
|
53
|
+
console = Console()
|
|
54
|
+
project_dir = os.getcwd()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class Status(Enum):
|
|
58
|
+
"""Enum used to describe status values."""
|
|
59
|
+
|
|
60
|
+
NEW = 20
|
|
61
|
+
REOPENED = 25
|
|
62
|
+
INPROGRESS = 30
|
|
63
|
+
CLOSED = 40
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class StatusColor(Enum):
|
|
67
|
+
"""Enum to describe colors used for status displays."""
|
|
68
|
+
|
|
69
|
+
NEW = "[cornsilk1]"
|
|
70
|
+
REOPENED = "[bright_yellow]"
|
|
71
|
+
INPROGRESS = "[deep_sky_blue1]"
|
|
72
|
+
CLOSED = "[bright_green]"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@click.group()
|
|
76
|
+
def crowdstrike():
|
|
77
|
+
"""[BETA] CrowdStrike Integration to load threat intelligence to RegScale."""
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@crowdstrike.command(name="query_incidents")
|
|
81
|
+
@regscale_id(help="RegScale will create and update issues as children of this record.")
|
|
82
|
+
@regscale_module()
|
|
83
|
+
@click.option(
|
|
84
|
+
"--filter",
|
|
85
|
+
type=click.STRING,
|
|
86
|
+
default=None,
|
|
87
|
+
hide_input=False,
|
|
88
|
+
required=False,
|
|
89
|
+
help="Falcon Query Language(FQL) string.",
|
|
90
|
+
)
|
|
91
|
+
def query_incidents(regscale_id: int, regscale_module: str, filter: str, limit=500) -> None:
|
|
92
|
+
"""Query Incidents from CrowdStrike."""
|
|
93
|
+
query_crowdstrike_incidents(regscale_id, regscale_module, filter, limit)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def determine_incident_level(fine_score: int) -> str:
|
|
97
|
+
"""
|
|
98
|
+
Determine the incident level based on a fine_score
|
|
99
|
+
|
|
100
|
+
:param int fine_score: The fine_score as an integer
|
|
101
|
+
:return: The incident level as a string
|
|
102
|
+
:rtype: str
|
|
103
|
+
"""
|
|
104
|
+
# Convert fine_score to the displayed score
|
|
105
|
+
displayed_score = fine_score / 10.0
|
|
106
|
+
|
|
107
|
+
if displayed_score >= 10.0:
|
|
108
|
+
return "S1 - High Severity"
|
|
109
|
+
elif displayed_score >= 8.0:
|
|
110
|
+
return "S1 - High Severity"
|
|
111
|
+
elif displayed_score >= 6.0:
|
|
112
|
+
return "S2 - Moderate Severity"
|
|
113
|
+
elif displayed_score >= 4.0:
|
|
114
|
+
return "S3 - Low Severity"
|
|
115
|
+
elif displayed_score >= 2.0:
|
|
116
|
+
return "S4 - Non-Incident"
|
|
117
|
+
else:
|
|
118
|
+
return "S5 - Uncategorized"
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def map_status_to_phase(status_code: int) -> str:
|
|
122
|
+
"""Map a CrowdStrike status code to a RegScale phase
|
|
123
|
+
|
|
124
|
+
:param int status_code: The status code from CrowdStrike as an integer.
|
|
125
|
+
:return: The corresponding phase in RegScale as a string.
|
|
126
|
+
:rtype: str
|
|
127
|
+
"""
|
|
128
|
+
crowdstrike_to_regcale_mapping = {
|
|
129
|
+
20: "Detection",
|
|
130
|
+
25: "Analysis",
|
|
131
|
+
30: "Containment",
|
|
132
|
+
40: "Closed",
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return crowdstrike_to_regcale_mapping.get(status_code, "Analysis")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def create_properties_for_incident(device_data: dict, regscale_id: int) -> None:
|
|
139
|
+
"""Create properties in RegScale based on the given device data dictionary
|
|
140
|
+
|
|
141
|
+
:param dict device_data: The device data as a dictionary.
|
|
142
|
+
:param int regscale_id: The parent ID of the incidents.
|
|
143
|
+
:rtype: None
|
|
144
|
+
"""
|
|
145
|
+
# Simulate RegScale API or database connection
|
|
146
|
+
properties = []
|
|
147
|
+
api = Api()
|
|
148
|
+
domain = api.config.get("domain")
|
|
149
|
+
url = urljoin(domain, "/api/properties/batchCreate")
|
|
150
|
+
for key, value in device_data.items():
|
|
151
|
+
# Skip list pieces
|
|
152
|
+
if not isinstance(value, list):
|
|
153
|
+
# Create property in RegScale (simulated)
|
|
154
|
+
prop = {
|
|
155
|
+
"isPublic": "true",
|
|
156
|
+
"key": key,
|
|
157
|
+
"value": value,
|
|
158
|
+
"parentId": regscale_id,
|
|
159
|
+
"parentModule": "incidents",
|
|
160
|
+
}
|
|
161
|
+
properties.append(prop)
|
|
162
|
+
response = api.post(url=url, json=properties)
|
|
163
|
+
if not response.ok:
|
|
164
|
+
logger.error("Failed to create property.")
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def get_existing_regscale_incidents(parent_id: int, parent_module: str) -> list[dict]:
|
|
168
|
+
"""Get existing RegScale incidents for the given parent ID and parent module
|
|
169
|
+
|
|
170
|
+
:param int parent_id: The parent ID of the incidents.
|
|
171
|
+
:param str parent_module: The parent module of the incidents.
|
|
172
|
+
:return: The existing incidents as a list of dictionaries.
|
|
173
|
+
:rtype: list[dict]
|
|
174
|
+
"""
|
|
175
|
+
existing_incidents = Incident.get_all_by_parent(parent_id, parent_module)
|
|
176
|
+
return [incident.dict() for incident in existing_incidents]
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def incidents_exist(title: str, existing_incidents: list[dict]) -> bool:
|
|
180
|
+
"""Determine if an incident already exists in RegScale
|
|
181
|
+
|
|
182
|
+
:param str title: The title of the incident.
|
|
183
|
+
:param list[dict] existing_incidents: The existing incidents as a list of dictionaries.
|
|
184
|
+
:return: If the incident already exists in RegScale
|
|
185
|
+
:rtype: bool
|
|
186
|
+
"""
|
|
187
|
+
if existing_incidents:
|
|
188
|
+
for existing_incident in existing_incidents:
|
|
189
|
+
if existing_incident["title"] == title:
|
|
190
|
+
logger.info(f"Incident {existing_incident['title']} already exists in RegScale.")
|
|
191
|
+
return True
|
|
192
|
+
return False
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def create_regscale_incidents(incidents: list[dict], regscale_id: int, regscale_module: str) -> None:
|
|
196
|
+
"""
|
|
197
|
+
Create Incidents in RegScale based on given Falcon incidents.
|
|
198
|
+
|
|
199
|
+
:param list[dict] incidents: List of Falcon Incident dictionaries
|
|
200
|
+
:param int regscale_id: RegScale parent ID
|
|
201
|
+
:param str regscale_module: RegScale parent module
|
|
202
|
+
:rtype: None
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
app = Application()
|
|
206
|
+
existing_incidents = get_existing_regscale_incidents(regscale_id, regscale_module)
|
|
207
|
+
logger.info(f"Found {len(existing_incidents)} existing incidents.")
|
|
208
|
+
user_id = app.config.get("userId")
|
|
209
|
+
|
|
210
|
+
for incident in track(
|
|
211
|
+
incidents,
|
|
212
|
+
description="[#f8b737]Comparing Crowdstrike and RegScale incident(s)...",
|
|
213
|
+
):
|
|
214
|
+
title = f"CrowdStrike incidentId: {incident['incident_id']}"
|
|
215
|
+
|
|
216
|
+
if not incidents_exist(title, existing_incidents):
|
|
217
|
+
regscale_incident = create_regscale_incident(incident, regscale_id, regscale_module, user_id)
|
|
218
|
+
post_incident_and_associate_properties(regscale_incident, incident, user_id)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def create_regscale_incident(
|
|
222
|
+
incident: dict,
|
|
223
|
+
regscale_id: int,
|
|
224
|
+
regscale_module: str,
|
|
225
|
+
user_id: Optional[str] = None,
|
|
226
|
+
) -> Incident:
|
|
227
|
+
"""
|
|
228
|
+
Creates a RegScale incident object from a Falcon incident
|
|
229
|
+
|
|
230
|
+
:param dict incident: Falcon incident dictionary
|
|
231
|
+
:param int regscale_id: RegScale parent ID
|
|
232
|
+
:param str regscale_module: RegScale parent module
|
|
233
|
+
:param Optional[str] user_id: User ID of the user performing the operation, defaults to None
|
|
234
|
+
:return: RegScale incident object
|
|
235
|
+
:rtype: Incident
|
|
236
|
+
"""
|
|
237
|
+
severity = determine_incident_level(incident["fine_score"])
|
|
238
|
+
source_cause = ", ".join(incident.get("techniques", "")) if incident else ""
|
|
239
|
+
|
|
240
|
+
return Incident(
|
|
241
|
+
title=f"CrowdStrike incidentId: {incident['incident_id']}",
|
|
242
|
+
severity=severity,
|
|
243
|
+
sourceCause=source_cause,
|
|
244
|
+
category="CAT 6 - Investigation",
|
|
245
|
+
phase=map_status_to_phase(incident["status"]),
|
|
246
|
+
description=format_data_to_html(incident), # Assuming this function formats the data to HTML
|
|
247
|
+
detectionMethod="Intrusion Detection System",
|
|
248
|
+
incidentPOCId=user_id,
|
|
249
|
+
dateDetected=incident["created"],
|
|
250
|
+
parentId=regscale_id if regscale_id is not None else None,
|
|
251
|
+
parentModule=regscale_module if regscale_module is not None else None,
|
|
252
|
+
lastUpdatedById=user_id,
|
|
253
|
+
dateCreated=incident["created"],
|
|
254
|
+
dateLastUpdated=get_current_datetime(),
|
|
255
|
+
dateResolved=(
|
|
256
|
+
incident["end"] if "end" in incident and map_status_to_phase(incident["status"]) == "Closed" else None
|
|
257
|
+
),
|
|
258
|
+
createdById=user_id,
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def post_incident_and_associate_properties(
|
|
263
|
+
regscale_incident: Incident, incident: dict, user_id: Optional[str] = None
|
|
264
|
+
) -> None:
|
|
265
|
+
"""
|
|
266
|
+
Posts the incident to RegScale and associates related properties
|
|
267
|
+
|
|
268
|
+
:param Incident regscale_incident: RegScale incident object
|
|
269
|
+
:param dict incident: Falcon incident dictionary
|
|
270
|
+
:param Optional[str] user_id: ID of the user performing the operation, defaults to None
|
|
271
|
+
:rtype: None
|
|
272
|
+
"""
|
|
273
|
+
response = Incident.post_incident(regscale_incident)
|
|
274
|
+
if response.ok:
|
|
275
|
+
incident_id = response.json()["id"]
|
|
276
|
+
create_and_associate_incident_properties(incident, incident_id, user_id)
|
|
277
|
+
logger.info(f"Created Incident: {regscale_incident.title} with ID: {incident_id}")
|
|
278
|
+
else:
|
|
279
|
+
response.raise_for_status()
|
|
280
|
+
logger.error(f"Failed to create Incident: {regscale_incident.title} in RegScale.")
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def create_and_associate_incident_properties(incident: dict, incident_id: int, user_id: Optional[str] = None) -> None:
|
|
284
|
+
"""
|
|
285
|
+
Creates and associates properties for a RegScale incident
|
|
286
|
+
|
|
287
|
+
:param dict incident: Falcon incident dictionary
|
|
288
|
+
:param int incident_id: ID of the RegScale incident
|
|
289
|
+
:param Optional[str] user_id: ID of the user performing the operation, defaults to None
|
|
290
|
+
:rtype: None
|
|
291
|
+
"""
|
|
292
|
+
for host in incident.get("hosts", []):
|
|
293
|
+
create_asset(data=host, parent_id=incident_id, parent_module="incidents", user_id=user_id)
|
|
294
|
+
|
|
295
|
+
for tactic in incident.get("tactics", []):
|
|
296
|
+
create_properties_for_incident({"tactic": tactic}, incident_id)
|
|
297
|
+
|
|
298
|
+
for technique in incident.get("techniques", []):
|
|
299
|
+
create_properties_for_incident({"technique": technique}, incident_id)
|
|
300
|
+
|
|
301
|
+
for objective in incident.get("objectives", []):
|
|
302
|
+
create_properties_for_incident({"objective": objective}, incident_id)
|
|
303
|
+
|
|
304
|
+
for user in incident.get("users", []):
|
|
305
|
+
create_properties_for_incident({"user": user}, incident_id)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def create_asset(data: dict, parent_id: int, parent_module: str, user_id: str) -> Dict:
|
|
309
|
+
"""
|
|
310
|
+
Create an asset in RegScale
|
|
311
|
+
|
|
312
|
+
:param dict data: The asset data as a dictionary
|
|
313
|
+
:param int parent_id: The parent ID in RegScale
|
|
314
|
+
:param str parent_module: The parent module in RegScale
|
|
315
|
+
:param str user_id: The user ID in RegScale
|
|
316
|
+
:return: Asset object as a dictionary
|
|
317
|
+
:rtype: Dict
|
|
318
|
+
"""
|
|
319
|
+
device_id = data.get("device_id", "")
|
|
320
|
+
asset = Asset(
|
|
321
|
+
parentId=parent_id,
|
|
322
|
+
parentModule=parent_module,
|
|
323
|
+
name=f'{data.get("hostname", device_id)} - {data.get("system_product_name", "Asset")}',
|
|
324
|
+
description=data.get("product_type_desc", None),
|
|
325
|
+
ipAddress=data.get("external_ip", None),
|
|
326
|
+
macAddress=data.get("mac_address", None),
|
|
327
|
+
manufacturer=data.get("bios_manufacturer", None),
|
|
328
|
+
model=data.get("bios_version", None),
|
|
329
|
+
serialNumber=data.get("serial-number", None),
|
|
330
|
+
assetCategory=regscale_models.AssetCategory.Hardware,
|
|
331
|
+
assetType="Desktop",
|
|
332
|
+
fqdn=data.get("fqdn", None),
|
|
333
|
+
notes=data.get("remarks", None),
|
|
334
|
+
operatingSystem=data.get("os_version", None),
|
|
335
|
+
osVersion=(
|
|
336
|
+
f"{data.get('major_version', None)}.{data.get('minor_version', None)}"
|
|
337
|
+
if data.get("major_version", None)
|
|
338
|
+
else None
|
|
339
|
+
),
|
|
340
|
+
netBIOS=data.get("netbios-name", None),
|
|
341
|
+
iPv6Address=data.get("ipv6-address", None),
|
|
342
|
+
ram=0,
|
|
343
|
+
diskStorage=0,
|
|
344
|
+
cpu=0,
|
|
345
|
+
assetOwnerId=user_id,
|
|
346
|
+
status="Active (On Network)",
|
|
347
|
+
isPublic=True,
|
|
348
|
+
dateCreated=get_current_datetime(),
|
|
349
|
+
dateLastUpdated=get_current_datetime(),
|
|
350
|
+
).create()
|
|
351
|
+
return asset.dict()
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def query_crowdstrike_incidents(regscale_id: int, regscale_module: str, filter: str, limit: int = 500) -> None:
|
|
355
|
+
"""
|
|
356
|
+
Query Incidents from CrowdStrike
|
|
357
|
+
|
|
358
|
+
:param int regscale_id: RegScale parent ID
|
|
359
|
+
:param str regscale_module: RegScale parent module
|
|
360
|
+
:param str filter: Falcon Query Language Filter
|
|
361
|
+
:param int limit: Record limit, 1-500, defaults to 500
|
|
362
|
+
:rtype: None
|
|
363
|
+
"""
|
|
364
|
+
incident_list = []
|
|
365
|
+
avail = True
|
|
366
|
+
offset = 500
|
|
367
|
+
while avail:
|
|
368
|
+
incidents = open_sdk().query_incidents(filter=filter, limit=limit, offset=offset)
|
|
369
|
+
logger.info(f"Found {len(incidents['body']['resources'])} incidents.")
|
|
370
|
+
if incidents["status_code"] != 200:
|
|
371
|
+
error_and_exit(incidents["body"]["errors"][0])
|
|
372
|
+
if not incidents["body"]["resources"]:
|
|
373
|
+
avail = False
|
|
374
|
+
else:
|
|
375
|
+
offset += limit
|
|
376
|
+
incident_list.extend(incidents["body"]["resources"])
|
|
377
|
+
if incident_list:
|
|
378
|
+
create_regscale_incidents(incident_list, regscale_id, regscale_module)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def open_sdk() -> Optional[Incidents]:
|
|
382
|
+
"""
|
|
383
|
+
Function to create an instance of the Crowdstrike SDK
|
|
384
|
+
|
|
385
|
+
:return: Incidents object
|
|
386
|
+
:rtype: Optional[Incidents]
|
|
387
|
+
"""
|
|
388
|
+
app = Application()
|
|
389
|
+
falcon_client_id = app.config.get("crowdstrikeClientId")
|
|
390
|
+
falcon_client_secret = app.config.get("crowdstrikeClientSecret")
|
|
391
|
+
try:
|
|
392
|
+
inc_object = Incidents(client_id=falcon_client_id, client_secret=falcon_client_secret)
|
|
393
|
+
if inc_object is not None:
|
|
394
|
+
logger.info("Successfully created Crowdstrike client.")
|
|
395
|
+
return inc_object
|
|
396
|
+
else:
|
|
397
|
+
logger.error("Unable to create Crowdstrike object.")
|
|
398
|
+
except AttributeError as aex:
|
|
399
|
+
logger.error(aex)
|
|
400
|
+
if str(aex) == """'str' object has no attribute 'authenticated'""":
|
|
401
|
+
error_and_exit("Unable to Authenticate with CrowdStrike API. Please check your credentials.")
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def users() -> UserManagement:
|
|
405
|
+
"""
|
|
406
|
+
Create instances of our two Service Classes and returns them
|
|
407
|
+
|
|
408
|
+
:return: UserManagement object
|
|
409
|
+
:rtype: UserManagement
|
|
410
|
+
"""
|
|
411
|
+
return UserManagement(auth_object=open_sdk())
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def get_incident_ids(sdk: Incidents, filter_string: Optional[str]) -> list:
|
|
415
|
+
"""
|
|
416
|
+
Retrieve all available incident IDs from Crowdstrike
|
|
417
|
+
|
|
418
|
+
:param Incidents sdk: Crowdstrike SDK object
|
|
419
|
+
:param Optional[str] filter_string: Filter string to use for query
|
|
420
|
+
:raises General Error: If unable to retrieve incident IDs from Crowdstrike
|
|
421
|
+
:return: List of incident IDs
|
|
422
|
+
:rtype: list
|
|
423
|
+
"""
|
|
424
|
+
params = {}
|
|
425
|
+
if filter_string:
|
|
426
|
+
params = {"filter": filter_string}
|
|
427
|
+
incident_id_lookup = sdk.query_incidents(**params)
|
|
428
|
+
if incident_id_lookup["status_code"] != 200:
|
|
429
|
+
error_and_exit(incident_id_lookup["body"]["errors"][0])
|
|
430
|
+
if not incident_id_lookup["body"]["resources"]:
|
|
431
|
+
logger.warning("No incidents found.")
|
|
432
|
+
|
|
433
|
+
return incident_id_lookup["body"]["resources"]
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def get_incident_data(id_list: list, sdk: Incidents) -> list[dict]:
|
|
437
|
+
"""Retrieve incident details using the IDs provided
|
|
438
|
+
|
|
439
|
+
:param list id_list: List of incident IDs from Crowdstrike
|
|
440
|
+
:param Incidents sdk: Crowdstrike SDK object
|
|
441
|
+
:raises General Error: If unable to retrieve incident details from Crowdstrike
|
|
442
|
+
:return: List of incident details
|
|
443
|
+
:rtype: list
|
|
444
|
+
"""
|
|
445
|
+
incident_detail_lookup = sdk.get_incidents(ids=id_list)
|
|
446
|
+
if incident_detail_lookup["status_code"] != 200:
|
|
447
|
+
error_and_exit(incident_detail_lookup["body"]["errors"][0])
|
|
448
|
+
return incident_detail_lookup["body"]["resources"]
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def tagging(inc_id: str, tags: list, untag: bool = False) -> None:
|
|
452
|
+
"""Assign or remove all tags provided
|
|
453
|
+
|
|
454
|
+
:param str inc_id: Incident ID to tag
|
|
455
|
+
:param list tags: List of tags to assign
|
|
456
|
+
:param bool untag: Flag to remove tags instead of assign, defaults to False
|
|
457
|
+
:rtype: None
|
|
458
|
+
"""
|
|
459
|
+
sdk = open_sdk()
|
|
460
|
+
action = {"ids": get_incident_full_id(inc_id)}
|
|
461
|
+
if untag:
|
|
462
|
+
action["delete_tag"] = tags
|
|
463
|
+
else:
|
|
464
|
+
action["add_tag"] = tags
|
|
465
|
+
change_result = sdk.perform_incident_action(**action)
|
|
466
|
+
if change_result["status_code"] != 200:
|
|
467
|
+
error_and_exit(change_result["body"]["errors"][0])
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def get_user_detail(uuid: str) -> str:
|
|
471
|
+
"""Retrieve assigned to user information for tabular display
|
|
472
|
+
|
|
473
|
+
:param str uuid: User ID to retrieve information for in CrowdStrike
|
|
474
|
+
:return: User information
|
|
475
|
+
:rtype: str
|
|
476
|
+
"""
|
|
477
|
+
lookup_result = users.retrieve_user(ids=uuid)
|
|
478
|
+
if lookup_result["status_code"] != 200:
|
|
479
|
+
error_and_exit(lookup_result["body"]["errors"][0])
|
|
480
|
+
user_info = lookup_result["body"]["resources"][0]
|
|
481
|
+
first = user_info["firstName"]
|
|
482
|
+
last = user_info["lastName"]
|
|
483
|
+
uid = user_info["uid"]
|
|
484
|
+
|
|
485
|
+
return f"{first} {last} ({uid})"
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def get_incident_full_id(partial: str) -> Union[str, bool]:
|
|
489
|
+
"""
|
|
490
|
+
Retrieve the full incident ID based off of the partial ID provided
|
|
491
|
+
|
|
492
|
+
:param str partial: Partial incident ID to search for
|
|
493
|
+
:raises General Error: If api call != 200
|
|
494
|
+
:raises General Error: If unable to find incident ID
|
|
495
|
+
:return: Full incident ID
|
|
496
|
+
:rtype: Union[str, bool]
|
|
497
|
+
"""
|
|
498
|
+
sdk = open_sdk()
|
|
499
|
+
search_result = sdk.query_incidents()
|
|
500
|
+
if search_result["status_code"] != 200:
|
|
501
|
+
error_and_exit(search_result["body"]["errors"][0])
|
|
502
|
+
found = False
|
|
503
|
+
for inc in search_result["body"]["resources"]:
|
|
504
|
+
incnum = inc.split(":")[2]
|
|
505
|
+
if incnum == partial:
|
|
506
|
+
found = inc
|
|
507
|
+
break
|
|
508
|
+
|
|
509
|
+
if not found:
|
|
510
|
+
error_and_exit("Unable to find incident ID specified.")
|
|
511
|
+
|
|
512
|
+
return found
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
def assignment(inc_id: str, assign_to: str = "", unassign: bool = False) -> None:
|
|
516
|
+
"""
|
|
517
|
+
Assign the incident specified to the user specified
|
|
518
|
+
|
|
519
|
+
:param str inc_id: Incident ID to assign
|
|
520
|
+
:param str assign_to: User ID to assign incident to
|
|
521
|
+
:param bool unassign: Flag to unassign incident, defaults to False
|
|
522
|
+
:raises General Error: If API Call != 200
|
|
523
|
+
:rtype: None
|
|
524
|
+
"""
|
|
525
|
+
sdk = open_sdk()
|
|
526
|
+
if unassign:
|
|
527
|
+
change_result = sdk.perform_incident_action(ids=get_incident_full_id(inc_id), unassign=True)
|
|
528
|
+
if change_result["status_code"] != 200:
|
|
529
|
+
error_and_exit(change_result["body"]["errors"][0])
|
|
530
|
+
else:
|
|
531
|
+
lookup_result = users.retrieve_user_uuid(uid=assign_to)
|
|
532
|
+
|
|
533
|
+
if lookup_result["status_code"] != 200:
|
|
534
|
+
error_and_exit(lookup_result["body"]["errors"][0])
|
|
535
|
+
change_result = sdk.perform_incident_action(
|
|
536
|
+
ids=get_incident_full_id(inc_id),
|
|
537
|
+
update_assigned_to_v2=lookup_result["body"]["resources"][0],
|
|
538
|
+
)
|
|
539
|
+
if change_result["status_code"] != 200:
|
|
540
|
+
error_and_exit(change_result["body"]["errors"][0])
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
def status_information(inc_data: dict) -> str:
|
|
544
|
+
"""
|
|
545
|
+
Parse status information for tabular display
|
|
546
|
+
|
|
547
|
+
:param dict inc_data: Incident data to parse
|
|
548
|
+
:return: Status information
|
|
549
|
+
:rtype: str
|
|
550
|
+
"""
|
|
551
|
+
inc_status = [
|
|
552
|
+
f"{StatusColor[Status(inc_data['status']).name].value}"
|
|
553
|
+
f"{Status(inc_data['status']).name.title().replace('Inp', 'InP')}[/]"
|
|
554
|
+
]
|
|
555
|
+
tag_list = inc_data.get("tags", [])
|
|
556
|
+
if tag_list:
|
|
557
|
+
inc_status.append(" ")
|
|
558
|
+
tag_list = [f"[magenta]{tg}[/]" for tg in tag_list]
|
|
559
|
+
inc_status.extend(tag_list)
|
|
560
|
+
|
|
561
|
+
return "\n".join(inc_status)
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
def incident_information(inc_data: dict) -> str:
|
|
565
|
+
"""
|
|
566
|
+
Parse incident overview information for tabular display
|
|
567
|
+
|
|
568
|
+
:param dict inc_data: Incident data to parse
|
|
569
|
+
:return: Incident overview information
|
|
570
|
+
:rtype: str
|
|
571
|
+
"""
|
|
572
|
+
inc_info = []
|
|
573
|
+
inc_info.append(inc_data.get("name", ""))
|
|
574
|
+
inc_info.append(f"[bold]{inc_data['incident_id'].split(':')[2]}[/]")
|
|
575
|
+
inc_info.append(f"Start: {inc_data.get('start', 'Unknown').replace('T', ' ')}")
|
|
576
|
+
inc_info.append(f" End: {inc_data.get('end', 'Unknown').replace('T', ' ')}")
|
|
577
|
+
if assigned := inc_data.get("assigned_to"):
|
|
578
|
+
inc_info.append("\n[underline]Assignment[/]")
|
|
579
|
+
inc_info.append(get_user_detail(assigned))
|
|
580
|
+
if inc_data.get("description"):
|
|
581
|
+
inc_info.append(" ")
|
|
582
|
+
inc_info.append(chunk_long_description(inc_data["description"], 50))
|
|
583
|
+
|
|
584
|
+
return "\n".join(inc_info)
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
def chunk_long_description(desc: str, col_width: int) -> str:
|
|
588
|
+
"""
|
|
589
|
+
Chunk a long string by delimiting with CR based upon column length
|
|
590
|
+
|
|
591
|
+
:param str desc: Description to parse
|
|
592
|
+
:param int col_width: Column width to chunk by
|
|
593
|
+
:return: Chunked description
|
|
594
|
+
:rtype: str
|
|
595
|
+
"""
|
|
596
|
+
desc_chunks = []
|
|
597
|
+
chunk = ""
|
|
598
|
+
for word in desc.split():
|
|
599
|
+
new_chunk = f"{chunk}{word.strip()} "
|
|
600
|
+
if len(new_chunk) >= col_width:
|
|
601
|
+
desc_chunks.append(new_chunk)
|
|
602
|
+
chunk = ""
|
|
603
|
+
else:
|
|
604
|
+
chunk = new_chunk
|
|
605
|
+
|
|
606
|
+
delim = "\n"
|
|
607
|
+
desc_chunks.append(chunk)
|
|
608
|
+
|
|
609
|
+
return delim.join(desc_chunks)
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
def hosts_information(inc_data: dict) -> str:
|
|
613
|
+
"""
|
|
614
|
+
Parse hosts information for tabular display
|
|
615
|
+
|
|
616
|
+
:param dict inc_data: Incident data to parse
|
|
617
|
+
:return: Host information
|
|
618
|
+
:rtype: str
|
|
619
|
+
"""
|
|
620
|
+
returned = ""
|
|
621
|
+
if "hosts" in inc_data:
|
|
622
|
+
host_str = []
|
|
623
|
+
for host in inc_data["hosts"]:
|
|
624
|
+
host_info = []
|
|
625
|
+
host_info.append(
|
|
626
|
+
f"<strong>{host.get('hostname', 'Unidentified')}</strong>"
|
|
627
|
+
f" ({host.get('platform_name', 'Not available')})"
|
|
628
|
+
)
|
|
629
|
+
host_info.append(f"<span style='color:cyan'>{host.get('device_id', 'Not available')}</span>")
|
|
630
|
+
host_info.append(f" Int: {host.get('local_ip', 'Not available')}")
|
|
631
|
+
host_info.append(f" Ext: {host.get('external_ip', 'Not available')}")
|
|
632
|
+
first = host.get("first_seen", "Unavailable").replace("T", " ").replace("Z", " ")
|
|
633
|
+
host_info.append(f"First: {first}")
|
|
634
|
+
last = host.get("last_seen", "Unavailable").replace("T", " ").replace("Z", " ")
|
|
635
|
+
host_info.append(f" Last: {last}")
|
|
636
|
+
host_str.append("\n".join(host_info))
|
|
637
|
+
if host_str:
|
|
638
|
+
returned = "\n".join(host_str)
|
|
639
|
+
else:
|
|
640
|
+
returned = "Unidentified"
|
|
641
|
+
|
|
642
|
+
return returned
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
def show_incident_table(incident_listing: list) -> None:
|
|
646
|
+
"""
|
|
647
|
+
Display all returned incidents in tabular fashion
|
|
648
|
+
|
|
649
|
+
:param list incident_listing: List of incidents to parse and print to console
|
|
650
|
+
:raises General Error: If incident_listing is empty
|
|
651
|
+
:rtype: None
|
|
652
|
+
"""
|
|
653
|
+
if not incident_listing:
|
|
654
|
+
error_and_exit("No incidents found, code 404")
|
|
655
|
+
table = Table(show_header=True, header_style="bold magenta", title="Incidents")
|
|
656
|
+
headers = {
|
|
657
|
+
"status": "[bold]Status[/] ",
|
|
658
|
+
"incident": "[bold]Incident[/]",
|
|
659
|
+
"hostname": "[bold]Host[/]",
|
|
660
|
+
"tactics": "[bold]Tactics[/]",
|
|
661
|
+
"techniques": "[bold]Techniques[/]",
|
|
662
|
+
"objectives": "[bold]Objective[/]s",
|
|
663
|
+
}
|
|
664
|
+
for value in headers.values():
|
|
665
|
+
table.add_column(value, justify="left")
|
|
666
|
+
for inc in incident_listing:
|
|
667
|
+
inc_detail = {"status": status_information(inc)}
|
|
668
|
+
inc_detail["incident"] = incident_information(inc)
|
|
669
|
+
inc_detail["hostname"] = hosts_information(inc)
|
|
670
|
+
inc_detail["tactics"] = "\n".join(inc["tactics"])
|
|
671
|
+
inc_detail["techniques"] = "\n".join(inc["techniques"])
|
|
672
|
+
inc_detail["objectives"] = "\n".join(inc["objectives"])
|
|
673
|
+
table.add_row(
|
|
674
|
+
inc_detail["status"],
|
|
675
|
+
inc_detail["incident"],
|
|
676
|
+
inc_detail["hostname"],
|
|
677
|
+
inc_detail["tactics"],
|
|
678
|
+
inc_detail["techniques"],
|
|
679
|
+
inc_detail["objectives"],
|
|
680
|
+
)
|
|
681
|
+
console.print(table)
|
|
682
|
+
|
|
683
|
+
|
|
684
|
+
def get_token() -> str:
|
|
685
|
+
"""
|
|
686
|
+
Get the token for the CrowdStrike API
|
|
687
|
+
|
|
688
|
+
:raises General Error: If unable to authenticate with CrowdStrike via API
|
|
689
|
+
:return: CrowdStrike API token
|
|
690
|
+
:rtype: str
|
|
691
|
+
"""
|
|
692
|
+
app = Application()
|
|
693
|
+
falcon_client_id = app.config.get("crowdstrikeClientId")
|
|
694
|
+
falcon_client_secret = app.config.get("crowdstrikeClientSecret")
|
|
695
|
+
falcon_url = app.config.get("crowdstrikeBaseUrl")
|
|
696
|
+
|
|
697
|
+
if not falcon_client_id:
|
|
698
|
+
falcon_client_id = click.prompt("Please provide your Falcon Client API Key", hide_input=True)
|
|
699
|
+
if not falcon_client_secret:
|
|
700
|
+
falcon_client_secret = click.prompt("Please provide your Falcon Client API Secret", hide_input=True)
|
|
701
|
+
auth = OAuth2(
|
|
702
|
+
client_id=falcon_client_id,
|
|
703
|
+
client_secret=falcon_client_secret,
|
|
704
|
+
base_url=falcon_url,
|
|
705
|
+
)
|
|
706
|
+
# Generate a token
|
|
707
|
+
auth.token()
|
|
708
|
+
if auth.token_status != 201:
|
|
709
|
+
raise error_and_exit("Unable to authenticate with Crowdstrike!")
|
|
710
|
+
return auth.token_value
|
|
711
|
+
|
|
712
|
+
|
|
713
|
+
@crowdstrike.command(name="sync_incidents")
|
|
714
|
+
@regscale_id(help="RegScale will create and update incidents as children of this record.")
|
|
715
|
+
@regscale_module()
|
|
716
|
+
def sync_incidents(regscale_id: int, regscale_module: str):
|
|
717
|
+
"""Sync Incidents and Assets from CrowdStrike to RegScale."""
|
|
718
|
+
sync_incidents_to_regscale(regscale_id, regscale_module)
|
|
719
|
+
|
|
720
|
+
|
|
721
|
+
def sync_incidents_to_regscale(regscale_id: int, regscale_module: str) -> None:
|
|
722
|
+
"""
|
|
723
|
+
Sync Incidents and Assets from CrowdStrike to RegScale
|
|
724
|
+
|
|
725
|
+
:param int regscale_id: RegScale record ID
|
|
726
|
+
:param str regscale_module: RegScale Module
|
|
727
|
+
:rtype: None
|
|
728
|
+
"""
|
|
729
|
+
verify_provided_module(regscale_module)
|
|
730
|
+
sdk = open_sdk()
|
|
731
|
+
incident_id_list = get_incident_ids(filter_string=None, sdk=sdk)
|
|
732
|
+
if not incident_id_list:
|
|
733
|
+
error_and_exit("No incidents found!")
|
|
734
|
+
incidents = get_incident_data(id_list=incident_id_list, sdk=sdk)
|
|
735
|
+
logger.info(f"Found {len(incidents)} incidents to sync.")
|
|
736
|
+
create_regscale_incidents(incidents=incidents, regscale_id=regscale_id, regscale_module=regscale_module)
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
def get_intel() -> Intel:
|
|
740
|
+
"""
|
|
741
|
+
Get the Intel SDK object
|
|
742
|
+
|
|
743
|
+
:return: Intel SDK object
|
|
744
|
+
:rtype: Intel
|
|
745
|
+
"""
|
|
746
|
+
app = Application()
|
|
747
|
+
client_id = app.config.get("crowdstrikeClientId")
|
|
748
|
+
client_secret = app.config.get("crowdstrikeClientSecret")
|
|
749
|
+
intel = Intel(
|
|
750
|
+
client_id=client_id,
|
|
751
|
+
client_secret=client_secret,
|
|
752
|
+
)
|
|
753
|
+
return intel
|
|
754
|
+
|
|
755
|
+
|
|
756
|
+
def get_vulnerability_ids(intel, limit=100) -> list:
|
|
757
|
+
"""Fetch ID's from CrowdStrike for Intel Model
|
|
758
|
+
|
|
759
|
+
:param Intel intel: The Intel SDK object
|
|
760
|
+
:param int limit: The number of records to fetch from CrowdStrike, defaults to 100
|
|
761
|
+
:raises General Error: If errors are returned from CrowdStrike
|
|
762
|
+
:raises Exception: If unable to fetch ID's from CrowdStrike
|
|
763
|
+
:return: List of vulnerability ID's
|
|
764
|
+
:rtype: list
|
|
765
|
+
"""
|
|
766
|
+
try:
|
|
767
|
+
id_lookup = intel.query_vulnerabilities(limit=limit)
|
|
768
|
+
if "errors" in id_lookup["body"]:
|
|
769
|
+
error_and_exit(id_lookup["body"]["errors"])
|
|
770
|
+
number_of_records = len(id_lookup["body"]["resources"])
|
|
771
|
+
if number_of_records == 0:
|
|
772
|
+
logger.info(f"Found {number_of_records} Records.")
|
|
773
|
+
sys.exit(0)
|
|
774
|
+
return id_lookup["body"]["resources"]
|
|
775
|
+
except Exception as e:
|
|
776
|
+
error_and_exit(f"Error: {e}")
|
|
777
|
+
|
|
778
|
+
|
|
779
|
+
def get_vulnerabilities_by_id(ids: list, intel: Intel) -> list[dict]:
|
|
780
|
+
"""
|
|
781
|
+
Retrieve record details using the IDs provided
|
|
782
|
+
|
|
783
|
+
:param list ids: The IDs of the records to retrieve
|
|
784
|
+
:param Intel intel: The Intel SDK object
|
|
785
|
+
:return: A list of dictionaries containing the record details
|
|
786
|
+
:rtype: list[dict]
|
|
787
|
+
"""
|
|
788
|
+
detail_lookup = intel.get_vulnerabilities(ids=ids)
|
|
789
|
+
if detail_lookup["status_code"] != 200:
|
|
790
|
+
error_and_exit(detail_lookup["body"]["errors"][0])
|
|
791
|
+
return detail_lookup["body"]["resources"]
|
|
792
|
+
|
|
793
|
+
|
|
794
|
+
# FIXME: this will pull the data from the CrowdStrike API but need to figure out what the data looks like before we can map it
|
|
795
|
+
# @crowdstrike.command(name="fetch_vulnerabilities")
|
|
796
|
+
# @regscale_id(help="RegScale will create vulnerabilities as children of this record.")
|
|
797
|
+
# @regscale_module()
|
|
798
|
+
# @click.option("--limit", "-l", default=100, help="Limit the number of records")
|
|
799
|
+
# def sync_vulnerabilities(regscale_id: int, regscale_module: str, limit: int) -> None:
|
|
800
|
+
# """
|
|
801
|
+
# Fetch all vulnerabilities from CrowdStrike.
|
|
802
|
+
# :param int regscale_id: The ID of the RegScale record.
|
|
803
|
+
# :param str regscale_module: The module of the RegScale record.
|
|
804
|
+
# :param int limit: The number of records to fetch.
|
|
805
|
+
# """
|
|
806
|
+
# _sync_vulnerabilities(regscale_id=regscale_id, regscale_module=regscale_module, limit=limit)
|
|
807
|
+
#
|
|
808
|
+
#
|
|
809
|
+
# def _sync_vulnerabilities(regscale_id: int, regscale_module: str, limit) -> None:
|
|
810
|
+
# """
|
|
811
|
+
# Fetch all vulnerabilities from CrowdStrike.
|
|
812
|
+
# :param int regscale_id: The ID of the RegScale record.
|
|
813
|
+
# :param str regscale_module: The module of the RegScale record.
|
|
814
|
+
# :param int limit: The number of records to fetch.
|
|
815
|
+
# """
|
|
816
|
+
# intel = get_intel()
|
|
817
|
+
# ids = get_vulnerability_ids(intel=intel, limit=limit)
|
|
818
|
+
# data = get_vulnerabilities_by_id(ids=ids, intel=intel)
|
|
819
|
+
# logger.info(json.dumps(data, indent=4))
|
|
820
|
+
|
|
821
|
+
|
|
822
|
+
def load_compliance_data(framework_file: str) -> dict:
|
|
823
|
+
"""
|
|
824
|
+
Load compliance data for the given framework from the package resources
|
|
825
|
+
|
|
826
|
+
:param str framework_file: The framework to load the compliance data for
|
|
827
|
+
:return: The compliance data as a dictionary
|
|
828
|
+
:rtype: dict
|
|
829
|
+
"""
|
|
830
|
+
from importlib.resources import open_text
|
|
831
|
+
|
|
832
|
+
with open_text("regscale.integrations.commercial.mappings", f"{framework_file}.json") as f:
|
|
833
|
+
return json.load(f)
|
|
834
|
+
|
|
835
|
+
|
|
836
|
+
def sync_compliance(ssp_id: int, catalog_id: int, framework: str) -> None:
|
|
837
|
+
"""
|
|
838
|
+
Sync compliance data from CrowdStrike to RegScale
|
|
839
|
+
|
|
840
|
+
:param int ssp_id: The ID of the SSP record
|
|
841
|
+
:param int catalog_id: The ID of the catalog to use for the sync
|
|
842
|
+
:param str framework: The framework to use for the sync
|
|
843
|
+
:rtype: None
|
|
844
|
+
"""
|
|
845
|
+
catalog = Catalog.get_with_all_details(catalog_id=catalog_id)
|
|
846
|
+
if framework == "NIST800-53R5":
|
|
847
|
+
compliance_data = load_compliance_data("nist_800_53_r5_controls")
|
|
848
|
+
elif framework == "CSF":
|
|
849
|
+
compliance_data = load_compliance_data("csf_controls")
|
|
850
|
+
else:
|
|
851
|
+
logger.warning(f"Invalid framework: {framework}")
|
|
852
|
+
return
|
|
853
|
+
|
|
854
|
+
logger.info(f"Loading: {framework}")
|
|
855
|
+
logger.info(f"Mapping file loaded items mapped: {len(compliance_data)}")
|
|
856
|
+
cat_controls = catalog.get("controls", None)
|
|
857
|
+
if not cat_controls:
|
|
858
|
+
logger.error(f"No controls found for catalog {catalog_id}")
|
|
859
|
+
return
|
|
860
|
+
full_controls = {}
|
|
861
|
+
partial_controls = {}
|
|
862
|
+
for control in cat_controls:
|
|
863
|
+
compliance_control = compliance_data.get(control.get("controlId"), None)
|
|
864
|
+
|
|
865
|
+
if compliance_control and compliance_control.get("support") in [
|
|
866
|
+
"Full",
|
|
867
|
+
"Partial",
|
|
868
|
+
]:
|
|
869
|
+
notes = [f"<p>{note}</p>" for note in compliance_control.get("notes", [])]
|
|
870
|
+
if compliance_control.get("support") == "Full":
|
|
871
|
+
control["implementation"] = "\n".join(notes)
|
|
872
|
+
full_controls[control.get("controlId").lower()] = control
|
|
873
|
+
elif compliance_control.get("support") == "Partial":
|
|
874
|
+
control["implementation"] = "\n".join(notes)
|
|
875
|
+
partial_controls[control.get("controlId").lower()] = control
|
|
876
|
+
logger.info(f"found fully implemented controls len: {len(full_controls)}")
|
|
877
|
+
logger.info(f"found partial implemented controls len: {len(partial_controls)}")
|
|
878
|
+
create_control_implementations(
|
|
879
|
+
cat_controls,
|
|
880
|
+
parent_id=ssp_id,
|
|
881
|
+
parent_module="securityplans",
|
|
882
|
+
existing_implementation_dict=ControlImplementation.get_existing_control_implementations(parent_id=ssp_id),
|
|
883
|
+
full_controls=full_controls,
|
|
884
|
+
partial_controls=partial_controls,
|
|
885
|
+
failing_controls={},
|
|
886
|
+
)
|
|
887
|
+
|
|
888
|
+
|
|
889
|
+
@crowdstrike.command(name="sync_compliance")
|
|
890
|
+
@click.option("--ssp_id", "-s", type=click.INT, required=True, help="The ID of the SSP record.")
|
|
891
|
+
@click.option(
|
|
892
|
+
"--catalog_id",
|
|
893
|
+
"-c",
|
|
894
|
+
type=click.INT,
|
|
895
|
+
required=True,
|
|
896
|
+
help="The ID of the catalog to use for the sync.",
|
|
897
|
+
)
|
|
898
|
+
@click.option(
|
|
899
|
+
"--framework",
|
|
900
|
+
"-f",
|
|
901
|
+
type=click.Choice(["CSF", "NIST800-53R5"], case_sensitive=False),
|
|
902
|
+
help="Choose either CSF or NIST800-53R5",
|
|
903
|
+
)
|
|
904
|
+
def run_compliance_sync(
|
|
905
|
+
ssp_id: int,
|
|
906
|
+
catalog_id: int,
|
|
907
|
+
framework: str,
|
|
908
|
+
) -> None:
|
|
909
|
+
"""Run a compliance sync from CrowdStrike to RegScale."""
|
|
910
|
+
sync_compliance(ssp_id=ssp_id, catalog_id=catalog_id, framework=framework)
|
|
911
|
+
|
|
912
|
+
|
|
913
|
+
def create_control_implementations(
|
|
914
|
+
controls: list,
|
|
915
|
+
parent_id: int,
|
|
916
|
+
parent_module: str,
|
|
917
|
+
existing_implementation_dict: dict,
|
|
918
|
+
full_controls: dict,
|
|
919
|
+
partial_controls: dict,
|
|
920
|
+
failing_controls: dict,
|
|
921
|
+
) -> None:
|
|
922
|
+
"""
|
|
923
|
+
Creates and updates control implementations based on given controls
|
|
924
|
+
|
|
925
|
+
:param list controls: List of control details
|
|
926
|
+
:param int parent_id: Identifier for the parent control
|
|
927
|
+
:param str parent_module: Name of the parent module
|
|
928
|
+
:param dict existing_implementation_dict: Dictionary of existing implementations
|
|
929
|
+
:param dict full_controls: Dictionary of fully implemented controls
|
|
930
|
+
:param dict partial_controls: Dictionary of partially implemented controls
|
|
931
|
+
:param dict failing_controls: Dictionary of failing controls
|
|
932
|
+
:rtype: None
|
|
933
|
+
"""
|
|
934
|
+
app = Application()
|
|
935
|
+
user_id = app.config.get("userId")
|
|
936
|
+
|
|
937
|
+
to_create, to_update = process_controls(
|
|
938
|
+
controls,
|
|
939
|
+
parent_id,
|
|
940
|
+
parent_module,
|
|
941
|
+
existing_implementation_dict,
|
|
942
|
+
full_controls,
|
|
943
|
+
partial_controls,
|
|
944
|
+
failing_controls,
|
|
945
|
+
user_id,
|
|
946
|
+
)
|
|
947
|
+
|
|
948
|
+
post_batch_if_needed(app, to_create, ControlImplementation.post_batch_implementation)
|
|
949
|
+
put_batch_if_needed(app, to_update, ControlImplementation.put_batch_implementation)
|
|
950
|
+
|
|
951
|
+
|
|
952
|
+
def process_controls(
|
|
953
|
+
controls: list,
|
|
954
|
+
parent_id: int,
|
|
955
|
+
parent_module: str,
|
|
956
|
+
existing_implementation_dict: dict,
|
|
957
|
+
full_controls: dict,
|
|
958
|
+
partial_controls: dict,
|
|
959
|
+
failing_controls: dict,
|
|
960
|
+
user_id: Optional[str] = None,
|
|
961
|
+
) -> tuple[list, list]:
|
|
962
|
+
"""
|
|
963
|
+
Processes each control for creation or update
|
|
964
|
+
|
|
965
|
+
:param list controls: List of control details
|
|
966
|
+
:param int parent_id: Identifier for the parent control
|
|
967
|
+
:param str parent_module: Name of the parent module
|
|
968
|
+
:param dict existing_implementation_dict: Dictionary of existing implementations
|
|
969
|
+
:param dict full_controls: Dictionary of fully implemented controls
|
|
970
|
+
:param dict partial_controls: Dictionary of partially implemented controls
|
|
971
|
+
:param dict failing_controls: Dictionary of failing controls
|
|
972
|
+
:param Optional[str] user_id: ID of the user performing the operation, defaults to None
|
|
973
|
+
:return: Tuple containing lists of controls to create and update
|
|
974
|
+
:rtype: tuple[list, list]
|
|
975
|
+
"""
|
|
976
|
+
to_create = []
|
|
977
|
+
to_update = []
|
|
978
|
+
|
|
979
|
+
for control in controls:
|
|
980
|
+
lower_case_control_id = control["controlId"].lower()
|
|
981
|
+
status = check_implementation(full_controls, partial_controls, failing_controls, lower_case_control_id)
|
|
982
|
+
|
|
983
|
+
if control["controlId"] not in existing_implementation_dict:
|
|
984
|
+
cim = create_new_control_implementation(control, parent_id, parent_module, status, user_id)
|
|
985
|
+
to_create.append(cim)
|
|
986
|
+
else:
|
|
987
|
+
update_existing_control_implementation(control, existing_implementation_dict, status, to_update, user_id)
|
|
988
|
+
|
|
989
|
+
return to_create, to_update
|
|
990
|
+
|
|
991
|
+
|
|
992
|
+
def create_new_control_implementation(
|
|
993
|
+
control: dict,
|
|
994
|
+
parent_id: int,
|
|
995
|
+
parent_module: str,
|
|
996
|
+
status: str,
|
|
997
|
+
user_id: Optional[str] = None,
|
|
998
|
+
) -> ControlImplementation:
|
|
999
|
+
"""
|
|
1000
|
+
Creates a new control implementation object
|
|
1001
|
+
|
|
1002
|
+
:param dict control: Control details
|
|
1003
|
+
:param int parent_id: Identifier for the parent control
|
|
1004
|
+
:param str parent_module: Name of the parent module
|
|
1005
|
+
:param str status: Status of the control implementation
|
|
1006
|
+
:param Optional[str] user_id: ID of the user performing the operation, defaults to None
|
|
1007
|
+
:return: New control implementation object
|
|
1008
|
+
:rtype: ControlImplementation
|
|
1009
|
+
"""
|
|
1010
|
+
cim = ControlImplementation(
|
|
1011
|
+
controlOwnerId=user_id,
|
|
1012
|
+
dateLastAssessed=get_current_datetime(),
|
|
1013
|
+
implementation=control.get("implementation", None),
|
|
1014
|
+
status=status,
|
|
1015
|
+
controlID=control["id"],
|
|
1016
|
+
parentId=parent_id,
|
|
1017
|
+
parentModule=parent_module,
|
|
1018
|
+
createdById=user_id,
|
|
1019
|
+
dateCreated=get_current_datetime(),
|
|
1020
|
+
lastUpdatedById=user_id,
|
|
1021
|
+
dateLastUpdated=get_current_datetime(),
|
|
1022
|
+
).dict()
|
|
1023
|
+
cim["controlSource"] = "Baseline"
|
|
1024
|
+
return cim
|
|
1025
|
+
|
|
1026
|
+
|
|
1027
|
+
def update_existing_control_implementation(
|
|
1028
|
+
control: dict,
|
|
1029
|
+
existing_implementation_dict: dict,
|
|
1030
|
+
status: str,
|
|
1031
|
+
to_update: list,
|
|
1032
|
+
user_id: Optional[str] = None,
|
|
1033
|
+
):
|
|
1034
|
+
"""
|
|
1035
|
+
Updates an existing control implementation
|
|
1036
|
+
|
|
1037
|
+
:param dict control: Control details
|
|
1038
|
+
:param dict existing_implementation_dict: Dictionary of existing implementations
|
|
1039
|
+
:param str status: Status of the control implementation
|
|
1040
|
+
:param list to_update: List of controls to update
|
|
1041
|
+
:param Optional[str] user_id: ID of the user performing the operation, defaults to None
|
|
1042
|
+
"""
|
|
1043
|
+
existing_imp = existing_implementation_dict[control["controlId"]]
|
|
1044
|
+
existing_imp.update(
|
|
1045
|
+
{
|
|
1046
|
+
"implementation": control.get("implementation"),
|
|
1047
|
+
"status": status,
|
|
1048
|
+
"dateLastAssessed": get_current_datetime(),
|
|
1049
|
+
"lastUpdatedById": user_id,
|
|
1050
|
+
"dateLastUpdated": get_current_datetime(),
|
|
1051
|
+
}
|
|
1052
|
+
)
|
|
1053
|
+
|
|
1054
|
+
remove_keys(existing_imp, ["createdBy", "systemRole", "controlOwner", "lastUpdatedBy"])
|
|
1055
|
+
|
|
1056
|
+
if existing_imp not in to_update:
|
|
1057
|
+
to_update.append(existing_imp)
|
|
1058
|
+
|
|
1059
|
+
|
|
1060
|
+
def post_batch_if_needed(
|
|
1061
|
+
app: Application,
|
|
1062
|
+
to_create: list,
|
|
1063
|
+
post_function: Callable[[Application, list], None],
|
|
1064
|
+
) -> None:
|
|
1065
|
+
"""
|
|
1066
|
+
Posts a batch of new implementations if the list is not empty
|
|
1067
|
+
|
|
1068
|
+
:param Application app: RegScale CLI application object
|
|
1069
|
+
:param list to_create: List of new implementations to post
|
|
1070
|
+
:param Callable[[Application, list], None] post_function: The function to call for posting the batch
|
|
1071
|
+
:rtype: None
|
|
1072
|
+
"""
|
|
1073
|
+
if to_create:
|
|
1074
|
+
post_function(app, to_create)
|
|
1075
|
+
|
|
1076
|
+
|
|
1077
|
+
def put_batch_if_needed(app: Application, to_update: list, put_function: Callable[[Application, list], None]) -> None:
|
|
1078
|
+
"""
|
|
1079
|
+
Puts a batch of updated implementations if the list is not empty
|
|
1080
|
+
|
|
1081
|
+
:param Application app: RegScale CLI application object
|
|
1082
|
+
:param list to_update: List of implementations to update
|
|
1083
|
+
:param Callable[[Application, list], None] put_function: The function to call for putting the batch
|
|
1084
|
+
:rtype: None
|
|
1085
|
+
"""
|
|
1086
|
+
if to_update:
|
|
1087
|
+
put_function(app, to_update)
|
|
1088
|
+
|
|
1089
|
+
|
|
1090
|
+
def check_implementation(
|
|
1091
|
+
full_controls: dict,
|
|
1092
|
+
partial_controls: dict,
|
|
1093
|
+
failing_controls: dict,
|
|
1094
|
+
control_id: str,
|
|
1095
|
+
) -> str:
|
|
1096
|
+
"""
|
|
1097
|
+
Checks the status of a control implementation
|
|
1098
|
+
|
|
1099
|
+
:param dict full_controls: Dictionary of passing controls
|
|
1100
|
+
:param dict partial_controls: Dictionary of partially implemented controls
|
|
1101
|
+
:param dict failing_controls: Dictionary of failing control implementations
|
|
1102
|
+
:param str control_id: control id
|
|
1103
|
+
:return: status of control implementation
|
|
1104
|
+
:rtype: str
|
|
1105
|
+
"""
|
|
1106
|
+
if control_id in full_controls.keys():
|
|
1107
|
+
logger.debug(f"Found fully implemented control: {control_id}")
|
|
1108
|
+
return ControlImplementationStatus.FullyImplemented.value
|
|
1109
|
+
elif control_id in partial_controls.keys():
|
|
1110
|
+
logger.debug(f"Found partially implemented control: {control_id}")
|
|
1111
|
+
return ControlImplementationStatus.PartiallyImplemented.value
|
|
1112
|
+
elif control_id in failing_controls.keys():
|
|
1113
|
+
logger.debug(f"Found failing control: {control_id}")
|
|
1114
|
+
return ControlImplementationStatus.InRemediation.value
|
|
1115
|
+
else:
|
|
1116
|
+
logger.debug(f"Found not implemented control: {control_id}")
|
|
1117
|
+
return ControlImplementationStatus.NotImplemented.value
|