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,641 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Integrates CISA into RegScale"""
|
|
4
|
+
|
|
5
|
+
# standard python imports
|
|
6
|
+
import logging
|
|
7
|
+
import re
|
|
8
|
+
from concurrent.futures import ALL_COMPLETED, ThreadPoolExecutor, wait
|
|
9
|
+
from datetime import date, datetime
|
|
10
|
+
from typing import List, Optional, Tuple, Any, Dict
|
|
11
|
+
from urllib.error import URLError
|
|
12
|
+
from urllib.parse import urlparse
|
|
13
|
+
|
|
14
|
+
import click
|
|
15
|
+
import dateutil.parser as dparser
|
|
16
|
+
import requests
|
|
17
|
+
from bs4 import BeautifulSoup, Tag
|
|
18
|
+
from requests import exceptions
|
|
19
|
+
from rich.console import Console
|
|
20
|
+
|
|
21
|
+
from regscale.core.app.api import Api
|
|
22
|
+
from regscale.core.app.application import Application
|
|
23
|
+
from regscale.core.app.internal.login import is_valid
|
|
24
|
+
from regscale.models import Link, Threat
|
|
25
|
+
from regscale.core.app.utils.app_utils import error_and_exit
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger("regscale")
|
|
28
|
+
console = Console()
|
|
29
|
+
CISA_THREATS_URL = (
|
|
30
|
+
"https://www.cisa.gov/news-events/cybersecurity-advisories"
|
|
31
|
+
"?search_api_fulltext=&sort_by=field_release_date&f%5B0%5D="
|
|
32
|
+
"advisory_type%3A94&f%5B1%5D=release_date_year%3A"
|
|
33
|
+
)
|
|
34
|
+
CISA_KEV_URL = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json"
|
|
35
|
+
THREATS_SUFFIX = "/api/threats"
|
|
36
|
+
DEFAULT_STR = "See Link for details."
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@click.group()
|
|
40
|
+
def cisa():
|
|
41
|
+
"""Update CISA."""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@cisa.command(name="ingest_cisa_kev")
|
|
45
|
+
def ingest_cisa_kev():
|
|
46
|
+
"""Update RegScale threats with the latest Known Exploited \
|
|
47
|
+
Vulnerabilities (KEV) feed from cisa.gov."""
|
|
48
|
+
data = pull_cisa_kev()
|
|
49
|
+
update_regscale(data)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@cisa.command(name="ingest_cisa_alerts")
|
|
53
|
+
@click.option(
|
|
54
|
+
"--year",
|
|
55
|
+
type=click.INT,
|
|
56
|
+
help="Enter the year to search for CISA alerts.",
|
|
57
|
+
default=date.today().year,
|
|
58
|
+
show_default=True,
|
|
59
|
+
required=True,
|
|
60
|
+
)
|
|
61
|
+
def ingest_cisa_alerts(year: int):
|
|
62
|
+
"""Update RegScale threats with alerts from cisa.gov."""
|
|
63
|
+
alerts(year)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def update_regscale_links(threats: List[Threat]) -> None:
|
|
67
|
+
"""
|
|
68
|
+
Create RegScale links from Threat Responses
|
|
69
|
+
|
|
70
|
+
:param List[Threat] threats: List of Responses from RegScale API
|
|
71
|
+
:rtype: None
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
# extract url from html string using regex
|
|
75
|
+
def extract_url(html: str) -> str:
|
|
76
|
+
"""
|
|
77
|
+
Extract URL from HTML string
|
|
78
|
+
|
|
79
|
+
:param str html: HTML string
|
|
80
|
+
:return: URL
|
|
81
|
+
:rtype: str
|
|
82
|
+
"""
|
|
83
|
+
url = re.findall(r"(?P<url>https?://[^\s]+)", html)
|
|
84
|
+
return url[0].replace('"', "") if url else None
|
|
85
|
+
|
|
86
|
+
links = []
|
|
87
|
+
for threat in threats:
|
|
88
|
+
url = extract_url(threat.description)
|
|
89
|
+
if threat.description:
|
|
90
|
+
link = Link(
|
|
91
|
+
parentID=threat.id,
|
|
92
|
+
parentModule="threats",
|
|
93
|
+
url=url,
|
|
94
|
+
title=threat.title,
|
|
95
|
+
)
|
|
96
|
+
links.append(link)
|
|
97
|
+
Link.batch_update(links)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def process_threats(threats: list[Threat], unique_threats: set[str], reg_threats: list[Threat]) -> Tuple[list, list]:
|
|
101
|
+
"""
|
|
102
|
+
Process threats
|
|
103
|
+
|
|
104
|
+
:param list[Threat] threats: List of threats to process
|
|
105
|
+
:param set[str] unique_threats: Set of unique threat descriptions
|
|
106
|
+
:param list[Threat] reg_threats: List of RegScale threats
|
|
107
|
+
:return: Tuple of insert and update threats
|
|
108
|
+
:rtype: Tuple[list, list]
|
|
109
|
+
"""
|
|
110
|
+
insert_threats = []
|
|
111
|
+
update_threats = []
|
|
112
|
+
for threat in threats:
|
|
113
|
+
if threat and threat.description in unique_threats:
|
|
114
|
+
old_dict = [reg.dict() for reg in reg_threats if reg.description == threat.description][0]
|
|
115
|
+
update_dict = threat.dict()
|
|
116
|
+
update_dict = merge_old(update_dict, old_dict)
|
|
117
|
+
update_threats.append(update_dict) # Update
|
|
118
|
+
else:
|
|
119
|
+
if threat:
|
|
120
|
+
insert_threats.append(threat.dict()) # Post
|
|
121
|
+
return insert_threats, update_threats
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def alerts(year: int) -> None:
|
|
125
|
+
"""
|
|
126
|
+
Return CISA alerts for the specified year
|
|
127
|
+
|
|
128
|
+
:param int year: A target year for CISA alerts
|
|
129
|
+
:rtype: None
|
|
130
|
+
"""
|
|
131
|
+
app = Application()
|
|
132
|
+
update_threats = []
|
|
133
|
+
insert_threats = []
|
|
134
|
+
# Check to make sure we have a valid token
|
|
135
|
+
if not is_valid(app=app):
|
|
136
|
+
error_and_exit("Login Error: Invalid Credentials, please login for a new token.")
|
|
137
|
+
|
|
138
|
+
reg_threats = Threat.fetch_all_threats()
|
|
139
|
+
unique_threats = {reg.description for reg in reg_threats}
|
|
140
|
+
logger.info("Fetching CISA threats for %i...", year)
|
|
141
|
+
threats = parse_html(CISA_THREATS_URL + str(year), app=app)
|
|
142
|
+
logger.info("Found %i threats from CISA.", len(threats))
|
|
143
|
+
|
|
144
|
+
if len(threats) > 0:
|
|
145
|
+
logger.info("Processing %i threat(s)...", len(threats))
|
|
146
|
+
insert_threats, update_threats = process_threats(threats, unique_threats, reg_threats)
|
|
147
|
+
|
|
148
|
+
logging.getLogger("urllib3").propagate = False
|
|
149
|
+
if insert_threats:
|
|
150
|
+
logger.info("Inserting %i threats to RegScale...", len(insert_threats))
|
|
151
|
+
threats = Threat.bulk_insert(api=None, threats=insert_threats)
|
|
152
|
+
update_regscale_links(threats)
|
|
153
|
+
if update_threats:
|
|
154
|
+
update_regscale_threats(json_list=[Threat(**threat) for threat in update_threats])
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def parse_html(page_url: str, app: Application) -> list:
|
|
158
|
+
"""
|
|
159
|
+
Convert HTML from a given URL to a RegScale threat
|
|
160
|
+
|
|
161
|
+
:param str page_url: A URL to parse
|
|
162
|
+
:param Application app: Application object
|
|
163
|
+
:return: List of RegScale threats
|
|
164
|
+
:rtype: list
|
|
165
|
+
"""
|
|
166
|
+
control = {"page": 0, "items": 999, "links": []}
|
|
167
|
+
while control["items"] > 0:
|
|
168
|
+
soup = gen_soup(page_url + f"&page={control['page']}")
|
|
169
|
+
|
|
170
|
+
articles = soup.find_all("article")
|
|
171
|
+
for article in articles:
|
|
172
|
+
try:
|
|
173
|
+
title = article.text.strip("\n").replace("\n", " ").split("|")[1].strip(" ").replace(" ", " ")
|
|
174
|
+
except IndexError:
|
|
175
|
+
continue
|
|
176
|
+
short_description = ""
|
|
177
|
+
article_soup = article.find_all("a", href=True)
|
|
178
|
+
link = ("https://www.cisa.gov" + article_soup[0]["href"]) if article_soup else None
|
|
179
|
+
if is_url(link):
|
|
180
|
+
logger.debug("Short Description: %s", short_description)
|
|
181
|
+
control["links"].append((link, short_description, title))
|
|
182
|
+
logger.info("Building RegScale threat from %s.", link)
|
|
183
|
+
|
|
184
|
+
control["items"] = len(articles)
|
|
185
|
+
control["page"] += 1
|
|
186
|
+
# check if max threads <= 20 to prevent IP ban from CISA
|
|
187
|
+
max_threads = min(app.config["maxThreads"], 20)
|
|
188
|
+
with ThreadPoolExecutor(max_workers=max_threads) as executor:
|
|
189
|
+
futures = []
|
|
190
|
+
for link in control["links"]:
|
|
191
|
+
logger.debug("Building RegScale threat from %s.", link[0])
|
|
192
|
+
futures.append(
|
|
193
|
+
executor.submit(
|
|
194
|
+
build_threat,
|
|
195
|
+
app=app,
|
|
196
|
+
detailed_link=link[0],
|
|
197
|
+
short_description=link[1],
|
|
198
|
+
title=link[2],
|
|
199
|
+
)
|
|
200
|
+
)
|
|
201
|
+
wait(futures, return_when=ALL_COMPLETED)
|
|
202
|
+
threats = [future.result() for future in futures if future.result()]
|
|
203
|
+
# Log errors
|
|
204
|
+
for error_ix in (ix for (ix, fut) in enumerate(futures) if not fut.result()):
|
|
205
|
+
logger.warning("Unable to fetch: %s", control["links"][error_ix][0])
|
|
206
|
+
return threats
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def build_threat(app: Application, detailed_link: str, short_description: str, title: str) -> Threat:
|
|
210
|
+
"""
|
|
211
|
+
Parse HTML from a given URL/link and build a RegScale threat.
|
|
212
|
+
|
|
213
|
+
:param Application app: Application object
|
|
214
|
+
:param str detailed_link: URL of the CISA threat
|
|
215
|
+
:param str short_description: Description of the threat
|
|
216
|
+
:param str title: Title for the threat
|
|
217
|
+
:return: RegScale threat class
|
|
218
|
+
:rtype: Threat
|
|
219
|
+
"""
|
|
220
|
+
dat = parse_details(detailed_link)
|
|
221
|
+
threat = None
|
|
222
|
+
if dat:
|
|
223
|
+
date_created = dat[0]
|
|
224
|
+
vulnerability = dat[1]
|
|
225
|
+
mitigation = dat[2]
|
|
226
|
+
notes = dat[3]
|
|
227
|
+
|
|
228
|
+
threat = Threat(
|
|
229
|
+
uuid=Threat.xstr(None),
|
|
230
|
+
title=title,
|
|
231
|
+
threatType="Specific",
|
|
232
|
+
threatOwnerId=app.config["userId"],
|
|
233
|
+
dateIdentified=date_created,
|
|
234
|
+
targetType="Other",
|
|
235
|
+
source="Open Source",
|
|
236
|
+
description=short_description or f"""<p><a href="{detailed_link}" title="">{detailed_link}</a></p>""",
|
|
237
|
+
vulnerabilityAnalysis="".join(vulnerability),
|
|
238
|
+
mitigations="".join(mitigation),
|
|
239
|
+
notes="".join(notes),
|
|
240
|
+
dateCreated=date_created,
|
|
241
|
+
status="Initial Report/Notification",
|
|
242
|
+
)
|
|
243
|
+
return threat
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def filter_elements(element: Tag) -> Optional[Tag]:
|
|
247
|
+
"""
|
|
248
|
+
Filter elements
|
|
249
|
+
|
|
250
|
+
:param Tag element: A BeautifulSoup Tag
|
|
251
|
+
:return: The given tag if it is a Tag and has children
|
|
252
|
+
:rtype: Optional[Tag]
|
|
253
|
+
"""
|
|
254
|
+
filter_lst = [
|
|
255
|
+
"c-figure__media",
|
|
256
|
+
"c-product-survey__text-area",
|
|
257
|
+
"l-full__footer",
|
|
258
|
+
"usa-navbar",
|
|
259
|
+
]
|
|
260
|
+
found = False
|
|
261
|
+
if element.attrs.get("class"):
|
|
262
|
+
found = any(item in element.attrs["class"] for item in filter_lst)
|
|
263
|
+
if element.name in ("p", "li", "div", "table") and not found:
|
|
264
|
+
return element
|
|
265
|
+
return None
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def process_params(
|
|
269
|
+
element: Tag, nav_string: str, vulnerability: list, mitigation: list, notes: list
|
|
270
|
+
) -> Tuple[list, list, list]:
|
|
271
|
+
"""
|
|
272
|
+
Process Parameters
|
|
273
|
+
|
|
274
|
+
:param Tag element: A BeautifulSoup Tag
|
|
275
|
+
:param str nav_string: A string to filter on
|
|
276
|
+
:param list vulnerability: A list of vulnerabilities
|
|
277
|
+
:param list mitigation: A list of mitigations
|
|
278
|
+
:param list notes: A list of notes
|
|
279
|
+
:return: Tuple[vulnerability, mitigation, notes]
|
|
280
|
+
:rtype: Tuple[list, list, list]
|
|
281
|
+
"""
|
|
282
|
+
# Filter out UL, seems to be duplicated with li tag.
|
|
283
|
+
if filter_elements(element):
|
|
284
|
+
content = str(element)
|
|
285
|
+
if nav_string.lower() == "summary" and content not in notes:
|
|
286
|
+
notes.append(content)
|
|
287
|
+
if nav_string.lower() == "technical details" and content not in vulnerability:
|
|
288
|
+
vulnerability.append(content)
|
|
289
|
+
if nav_string.lower() == "mitigations" and content not in mitigation:
|
|
290
|
+
mitigation.append(content)
|
|
291
|
+
return vulnerability, mitigation, notes
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def process_element(*args) -> Tuple[dict, str, str]:
|
|
295
|
+
"""
|
|
296
|
+
Loop elements and determine last header, last_h3, and nav_string
|
|
297
|
+
|
|
298
|
+
:return: Tuple[last_header, last_h3, nav_string]
|
|
299
|
+
:rtype: Tuple[dict, str, str]
|
|
300
|
+
"""
|
|
301
|
+
# Unpack tuple args
|
|
302
|
+
(
|
|
303
|
+
dat,
|
|
304
|
+
last_header,
|
|
305
|
+
last_h3,
|
|
306
|
+
nav_string,
|
|
307
|
+
div_list,
|
|
308
|
+
vulnerability,
|
|
309
|
+
mitigation,
|
|
310
|
+
notes,
|
|
311
|
+
) = args[0]
|
|
312
|
+
|
|
313
|
+
last_header = {"type": dat.name, "title": dat.text} if re.match(r"^h[1-6]$", dat.name) else last_header
|
|
314
|
+
last_h3 = dat.text if dat.name == "h3" else last_h3
|
|
315
|
+
if last_header and isinstance(dat, Tag) and dat.text.lower() in div_list:
|
|
316
|
+
nav_string = dat.text.lower()
|
|
317
|
+
if last_h3 and nav_string and dat.text.lower().replace("\n", "") not in div_list and last_h3.lower() in div_list:
|
|
318
|
+
process_params(dat, nav_string, vulnerability, mitigation, notes)
|
|
319
|
+
return last_header, last_h3, nav_string
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def parse_details(link: str) -> Optional[Tuple[str, list, list, list]]:
|
|
323
|
+
"""
|
|
324
|
+
Parse the details of a given link
|
|
325
|
+
|
|
326
|
+
:param str link: A URL to parse
|
|
327
|
+
:return: A tuple of date created, vulnerability, mitigation, and notes
|
|
328
|
+
:rtype: Optional[Tuple[str, list, list, list]]
|
|
329
|
+
"""
|
|
330
|
+
div_list = ["technical details", "mitigations", "summary", "executive summary"]
|
|
331
|
+
vulnerability = []
|
|
332
|
+
mitigation = []
|
|
333
|
+
notes = []
|
|
334
|
+
detailed_soup = gen_soup(link)
|
|
335
|
+
date_created = fuzzy_find_date(detailed_soup)
|
|
336
|
+
last_header = None
|
|
337
|
+
last_h3 = None
|
|
338
|
+
nav_string = ""
|
|
339
|
+
|
|
340
|
+
for ele in detailed_soup.find_all("div", {"class": "l-full__main"}):
|
|
341
|
+
for dat in ele.find_all():
|
|
342
|
+
args = (
|
|
343
|
+
dat,
|
|
344
|
+
last_header,
|
|
345
|
+
last_h3,
|
|
346
|
+
nav_string,
|
|
347
|
+
div_list,
|
|
348
|
+
vulnerability,
|
|
349
|
+
mitigation,
|
|
350
|
+
notes,
|
|
351
|
+
)
|
|
352
|
+
last_header, last_h3, nav_string = process_element(args)
|
|
353
|
+
|
|
354
|
+
if len(vulnerability) == 0:
|
|
355
|
+
vulnerability.append(DEFAULT_STR)
|
|
356
|
+
if len(notes) == 0:
|
|
357
|
+
notes.append(DEFAULT_STR)
|
|
358
|
+
if len(mitigation) == 0:
|
|
359
|
+
mitigation.append(DEFAULT_STR)
|
|
360
|
+
if date_created and vulnerability and mitigation and notes:
|
|
361
|
+
return date_created, unique(vulnerability), unique(mitigation), unique(notes)
|
|
362
|
+
return None
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def fuzzy_find_date(detailed_soup: BeautifulSoup, location: int = 2, attempts: int = 0) -> str:
|
|
366
|
+
"""
|
|
367
|
+
Perform a fuzzy find to pull a date from a bs4 object
|
|
368
|
+
|
|
369
|
+
:param BeautifulSoup detailed_soup: A BeautifulSoup object representing a webpage
|
|
370
|
+
:param int location: The location of the date in the webpage, defaults to 2
|
|
371
|
+
:param int attempts: Number of attempts to find a date, defaults to 0
|
|
372
|
+
:return: An ISO-formatted datetime string
|
|
373
|
+
:rtype: str
|
|
374
|
+
"""
|
|
375
|
+
|
|
376
|
+
fuzzy_dt = None
|
|
377
|
+
|
|
378
|
+
try:
|
|
379
|
+
if matches := re.search(r"(Last Revised|Release Date)\s*(.*)", detailed_soup.text):
|
|
380
|
+
fuzzy_dt = dparser.parse(matches.group(2), fuzzy=True).isoformat()
|
|
381
|
+
return fuzzy_dt
|
|
382
|
+
fuzzy_dt = dparser.parse(
|
|
383
|
+
str(detailed_soup.find_all("div", {"class": "c-field__content"})[location].text)
|
|
384
|
+
.strip("\n")
|
|
385
|
+
.strip()
|
|
386
|
+
.split("|", maxsplit=1)[0]
|
|
387
|
+
.strip(),
|
|
388
|
+
fuzzy=True,
|
|
389
|
+
).isoformat()
|
|
390
|
+
except dparser.ParserError as pex:
|
|
391
|
+
logger.error("Error Processing Alert date created: %s.", pex)
|
|
392
|
+
if not fuzzy_dt and attempts < 5:
|
|
393
|
+
fuzzy_dt = fuzzy_find_date(detailed_soup, location + 1, attempts + 1)
|
|
394
|
+
if not fuzzy_dt and attempts >= 5:
|
|
395
|
+
logger.error("Unable to find date created in CISA alert.")
|
|
396
|
+
return fuzzy_dt
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def gen_soup(url: str) -> BeautifulSoup:
|
|
400
|
+
"""
|
|
401
|
+
Generate a BeautifulSoup instance for the given URL
|
|
402
|
+
|
|
403
|
+
:param str url: URL string
|
|
404
|
+
:raises: URLError if URL is invalid
|
|
405
|
+
:rtype: BeautifulSoup
|
|
406
|
+
"""
|
|
407
|
+
if isinstance(url, Tuple):
|
|
408
|
+
url = url[0]
|
|
409
|
+
if is_url(url):
|
|
410
|
+
req = Api().get(url)
|
|
411
|
+
req.raise_for_status()
|
|
412
|
+
content = req.content
|
|
413
|
+
return BeautifulSoup(content, "html.parser")
|
|
414
|
+
raise URLError("URL is invalid, exiting...")
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def _load_from_package() -> dict:
|
|
418
|
+
"""
|
|
419
|
+
Load the cisa_kev_data.json from the RegScale CLI package
|
|
420
|
+
|
|
421
|
+
:return: The cisa_kev_data.json
|
|
422
|
+
:rtype: dict
|
|
423
|
+
"""
|
|
424
|
+
import json
|
|
425
|
+
import importlib.resources as pkg_resources
|
|
426
|
+
|
|
427
|
+
# check if the filepath exists before trying to open it
|
|
428
|
+
with pkg_resources.open_text("regscale.models.integration_models", "cisa_kev_data.json") as file:
|
|
429
|
+
data = json.load(file)
|
|
430
|
+
return data
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def pull_cisa_kev() -> Dict[Any, Any]:
|
|
434
|
+
"""
|
|
435
|
+
Pull the latest Known Exploited Vulnerabilities (KEV) data from CISA
|
|
436
|
+
|
|
437
|
+
:return: Dictionary of known vulnerabilities via API
|
|
438
|
+
:rtype: Dict[Any, Any]
|
|
439
|
+
"""
|
|
440
|
+
# Use module-level variable for caching
|
|
441
|
+
if not hasattr(pull_cisa_kev, "_cached_data"):
|
|
442
|
+
app = Application()
|
|
443
|
+
api = Api()
|
|
444
|
+
config = app.config
|
|
445
|
+
result = []
|
|
446
|
+
|
|
447
|
+
# Get URL from config or use default
|
|
448
|
+
if "cisa_kev" in config:
|
|
449
|
+
cisa_url = config["cisaKev"]
|
|
450
|
+
else:
|
|
451
|
+
cisa_url = CISA_KEV_URL
|
|
452
|
+
config["cisaKev"] = cisa_url
|
|
453
|
+
app.save_config(config)
|
|
454
|
+
|
|
455
|
+
try:
|
|
456
|
+
response = api.get(url=cisa_url, headers={}, retry_login=False)
|
|
457
|
+
if not response:
|
|
458
|
+
logger.error("Failed to get response from %s\nUsing KEV data from RegScale CLI package.", cisa_url)
|
|
459
|
+
data = _load_from_package()
|
|
460
|
+
pull_cisa_kev._cached_data = data
|
|
461
|
+
return data
|
|
462
|
+
response.raise_for_status()
|
|
463
|
+
result = response.json()
|
|
464
|
+
|
|
465
|
+
# Cache the result
|
|
466
|
+
pull_cisa_kev._cached_data = result
|
|
467
|
+
|
|
468
|
+
except exceptions.RequestException as ex:
|
|
469
|
+
# Whoops it wasn't a 200
|
|
470
|
+
logger.error("Error retrieving CISA KEV data: %s.\nUsing KEV data from RegScale CLI package.", str(ex))
|
|
471
|
+
data = _load_from_package()
|
|
472
|
+
pull_cisa_kev._cached_data = data
|
|
473
|
+
return data
|
|
474
|
+
|
|
475
|
+
return pull_cisa_kev._cached_data
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def convert_date_string(date_str: str) -> str:
|
|
479
|
+
"""
|
|
480
|
+
Convert the given date string for use in RegScale
|
|
481
|
+
|
|
482
|
+
:param str date_str: date as a string
|
|
483
|
+
:return: RegScale accepted datetime string format
|
|
484
|
+
:rtype: str
|
|
485
|
+
"""
|
|
486
|
+
fmt = "%Y-%m-%d"
|
|
487
|
+
result_dt = datetime.strptime(date_str, fmt) # 2022-11-03 to 2022-08-23T03:00:39.925Z
|
|
488
|
+
return f"{result_dt.isoformat()}.000Z"
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
def update_regscale(data: dict) -> None:
|
|
492
|
+
"""
|
|
493
|
+
Update RegScale threats with the latest Known Exploited Vulnerabilities (KEV) data
|
|
494
|
+
|
|
495
|
+
:param dict data: Threat data from CISA
|
|
496
|
+
:rtype: None
|
|
497
|
+
"""
|
|
498
|
+
app = Application()
|
|
499
|
+
api = Api()
|
|
500
|
+
reg_threats = Threat.fetch_all_threats()
|
|
501
|
+
unique_threats = {reg.description for reg in reg_threats}
|
|
502
|
+
matching_threats = [d for d in data["vulnerabilities"] if d["vulnerabilityName"] in unique_threats]
|
|
503
|
+
threats_inserted = []
|
|
504
|
+
threats_updated = []
|
|
505
|
+
new_threats = [dat for dat in data["vulnerabilities"] if dat not in matching_threats]
|
|
506
|
+
console.print(f"Found {len(new_threats)} new threats from CISA")
|
|
507
|
+
if [dat for dat in data["vulnerabilities"] if dat not in matching_threats]:
|
|
508
|
+
for rec in new_threats:
|
|
509
|
+
threat = Threat(
|
|
510
|
+
uuid=Threat.xstr(None),
|
|
511
|
+
title=rec["cveID"],
|
|
512
|
+
threatType="Specific",
|
|
513
|
+
threatOwnerId=app.config["userId"],
|
|
514
|
+
dateIdentified=convert_date_string(rec["dateAdded"]),
|
|
515
|
+
targetType="Other",
|
|
516
|
+
source="Open Source",
|
|
517
|
+
description=rec["vulnerabilityName"],
|
|
518
|
+
vulnerabilityAnalysis=rec["shortDescription"],
|
|
519
|
+
mitigations=rec["requiredAction"],
|
|
520
|
+
notes=rec["notes"].strip() + " Due Date: " + rec["dueDate"],
|
|
521
|
+
dateCreated=(datetime.now()).isoformat(),
|
|
522
|
+
status="Initial Report/Notification",
|
|
523
|
+
)
|
|
524
|
+
threats_inserted.append(threat.dict())
|
|
525
|
+
update_threats = [dat for dat in data["vulnerabilities"] if dat in matching_threats]
|
|
526
|
+
if len(matching_threats) > 0:
|
|
527
|
+
for rec in update_threats:
|
|
528
|
+
update_vuln = Threat(
|
|
529
|
+
uuid=Threat.xstr(None),
|
|
530
|
+
title=rec["cveID"],
|
|
531
|
+
threatType="Specific",
|
|
532
|
+
threatOwnerId=app.config["userId"],
|
|
533
|
+
dateIdentified=convert_date_string(rec["dateAdded"]),
|
|
534
|
+
targetType="Other",
|
|
535
|
+
description=rec["vulnerabilityName"],
|
|
536
|
+
vulnerabilityAnalysis=rec["shortDescription"],
|
|
537
|
+
mitigations=rec["requiredAction"],
|
|
538
|
+
dateCreated=convert_date_string(rec["dateAdded"]),
|
|
539
|
+
).dict()
|
|
540
|
+
old_vuln = [threat.dict() for threat in reg_threats if threat.description == update_vuln["description"]][0]
|
|
541
|
+
update_vuln = merge_old(update_vuln=update_vuln, old_vuln=old_vuln)
|
|
542
|
+
if old_vuln:
|
|
543
|
+
threats_updated.append(update_vuln)
|
|
544
|
+
if len(threats_inserted) > 0:
|
|
545
|
+
logging.getLogger("urllib3").propagate = False
|
|
546
|
+
# Update Matching Threats
|
|
547
|
+
logger.info("Inserting %i threats to RegScale...", len(threats_inserted))
|
|
548
|
+
Threat.bulk_insert(api, threats_inserted)
|
|
549
|
+
update_regscale_threats(json_list=threats_updated)
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
def merge_old(update_vuln: dict, old_vuln: dict) -> dict:
|
|
553
|
+
"""
|
|
554
|
+
Merge dictionaries of old and updated vulnerabilities
|
|
555
|
+
|
|
556
|
+
:param dict update_vuln: An updated vulnerability dictionary
|
|
557
|
+
:param dict old_vuln: An old vulnerability dictionary
|
|
558
|
+
:return: A merged vulnerability dictionary
|
|
559
|
+
:rtype: dict
|
|
560
|
+
"""
|
|
561
|
+
update_vuln["id"] = old_vuln["id"]
|
|
562
|
+
update_vuln["uuid"] = old_vuln["uuid"]
|
|
563
|
+
update_vuln["status"] = old_vuln["status"]
|
|
564
|
+
update_vuln["source"] = old_vuln["source"]
|
|
565
|
+
update_vuln["threatType"] = old_vuln["threatType"]
|
|
566
|
+
update_vuln["threatOwnerId"] = old_vuln["threatOwnerId"]
|
|
567
|
+
update_vuln["notes"] = old_vuln["notes"]
|
|
568
|
+
update_vuln["targetType"] = old_vuln["targetType"]
|
|
569
|
+
update_vuln["dateCreated"] = old_vuln["dateCreated"]
|
|
570
|
+
update_vuln["isPublic"] = old_vuln["isPublic"]
|
|
571
|
+
update_vuln["investigated"] = old_vuln["investigated"]
|
|
572
|
+
if "investigationResults" in old_vuln.keys():
|
|
573
|
+
update_vuln["investigationResults"] = old_vuln["investigationResults"]
|
|
574
|
+
return update_vuln
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
def insert_or_upd_threat(threat: dict, app: Application, threat_id: int = None) -> requests.Response:
|
|
578
|
+
"""
|
|
579
|
+
Insert or update the given threats in RegScale
|
|
580
|
+
|
|
581
|
+
:param dict threat: RegScale threat
|
|
582
|
+
:param Application app: Application object
|
|
583
|
+
:param int threat_id: RegScale ID of the threat, defaults to none
|
|
584
|
+
:return: An API response based on the PUT or POST action
|
|
585
|
+
:rtype: requests.Response
|
|
586
|
+
"""
|
|
587
|
+
api = Api()
|
|
588
|
+
config = app.config
|
|
589
|
+
url_threats = config["domain"] + THREATS_SUFFIX
|
|
590
|
+
headers = {"Accept": "application/json", "Authorization": config["token"]}
|
|
591
|
+
return (
|
|
592
|
+
api.put(url=f"{url_threats}/{threat_id}", headers=headers, json=threat)
|
|
593
|
+
if threat_id
|
|
594
|
+
else api.post(url=url_threats, headers=headers, json=threat)
|
|
595
|
+
)
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
def update_regscale_threats(
|
|
599
|
+
json_list: Optional[list] = None,
|
|
600
|
+
) -> None:
|
|
601
|
+
"""
|
|
602
|
+
Update the given threats in RegScale via concurrent POST or PUT of multiple objects
|
|
603
|
+
|
|
604
|
+
:param Optional[list] json_list: list of threats to be updated, defaults to None
|
|
605
|
+
:rtype: None
|
|
606
|
+
"""
|
|
607
|
+
if json_list and len(json_list) > 0:
|
|
608
|
+
logger.info("Updating %i threats to RegScale...", len(json_list))
|
|
609
|
+
Threat.bulk_update(None, json_list)
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
def unique(lst: List[str]) -> List[str]:
|
|
613
|
+
"""
|
|
614
|
+
Make a list unique, but don't change the order
|
|
615
|
+
|
|
616
|
+
:param List[str] lst: List to make unique
|
|
617
|
+
:return: List with unique values
|
|
618
|
+
:rtype: List[str]
|
|
619
|
+
"""
|
|
620
|
+
unique_list = []
|
|
621
|
+
seen = set()
|
|
622
|
+
for item in lst:
|
|
623
|
+
if item not in seen:
|
|
624
|
+
unique_list.append(item)
|
|
625
|
+
seen.add(item)
|
|
626
|
+
return unique_list
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
def is_url(url: str) -> bool:
|
|
630
|
+
"""
|
|
631
|
+
Determines if the given string is a URL
|
|
632
|
+
|
|
633
|
+
:param str url: A candidate URL string
|
|
634
|
+
:return: Whether the given string is a valid URL
|
|
635
|
+
:rtype: bool
|
|
636
|
+
"""
|
|
637
|
+
try:
|
|
638
|
+
result = urlparse(url)
|
|
639
|
+
return all([result.scheme, result.netloc])
|
|
640
|
+
except ValueError:
|
|
641
|
+
return False
|