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,504 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module for Tenable vulnerability scanning integration.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import datetime
|
|
6
|
+
import json
|
|
7
|
+
import linecache
|
|
8
|
+
import logging
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Dict, Iterator, List, Optional, Tuple
|
|
11
|
+
|
|
12
|
+
from tenable.errors import TioExportsError
|
|
13
|
+
|
|
14
|
+
from regscale.core.app.utils.app_utils import get_current_datetime
|
|
15
|
+
from regscale.core.utils.date import datetime_obj
|
|
16
|
+
from regscale.integrations.commercial.nessus.nessus_utils import get_min_cvss_score, validate_nessus_severity
|
|
17
|
+
from regscale.integrations.commercial.tenablev2.authenticate import gen_tio
|
|
18
|
+
from regscale.integrations.commercial.tenablev2.stig_parsers import parse_stig_output
|
|
19
|
+
from regscale.integrations.commercial.tenablev2.utils import get_last_pull_epoch
|
|
20
|
+
from regscale.integrations.commercial.tenablev2.variables import TenableVariables
|
|
21
|
+
from regscale.integrations.scanner_integration import IntegrationAsset, IntegrationFinding, ScannerIntegration
|
|
22
|
+
from regscale.integrations.variables import ScannerVariables
|
|
23
|
+
from regscale.models import regscale_models
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger("regscale")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class TenableIntegration(ScannerIntegration):
|
|
29
|
+
"""Integration class for Tenable vulnerability scanning."""
|
|
30
|
+
|
|
31
|
+
title: str = "Tenable"
|
|
32
|
+
asset_identifier_field: str = "tenableId"
|
|
33
|
+
finding_severity_map: Dict[str, regscale_models.IssueSeverity] = {
|
|
34
|
+
"critical": regscale_models.IssueSeverity.Critical,
|
|
35
|
+
"high": regscale_models.IssueSeverity.High,
|
|
36
|
+
"medium": regscale_models.IssueSeverity.Moderate,
|
|
37
|
+
"low": regscale_models.IssueSeverity.Low,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
def __init__(self, plan_id: int, tenant_id: int = 1, tags: Optional[List[str]] = None, **kwargs):
|
|
41
|
+
"""
|
|
42
|
+
Initialize the TenableIntegration.
|
|
43
|
+
|
|
44
|
+
:param int plan_id: The ID of the security plan
|
|
45
|
+
:param int tenant_id: The ID of the tenant, defaults to 1
|
|
46
|
+
"""
|
|
47
|
+
super().__init__(plan_id, tenant_id)
|
|
48
|
+
self.client = None
|
|
49
|
+
self.tags = tags or []
|
|
50
|
+
|
|
51
|
+
def authenticate(self) -> None:
|
|
52
|
+
"""Authenticate to Tenable."""
|
|
53
|
+
self.client = gen_tio()
|
|
54
|
+
|
|
55
|
+
def fetch_assets(self, *args: Any, **kwargs: Any) -> Iterator[IntegrationAsset]:
|
|
56
|
+
"""
|
|
57
|
+
Fetches Tenable assets using the Tenable.io API
|
|
58
|
+
|
|
59
|
+
:yields: Iterator[IntegrationAsset]
|
|
60
|
+
"""
|
|
61
|
+
tags: List[Tuple[str, str]] = kwargs.get("tags", [])
|
|
62
|
+
# Create artifacts directory if not exist
|
|
63
|
+
Path.mkdir(Path("./artifacts"), exist_ok=True, parents=True)
|
|
64
|
+
current_datetime = datetime.datetime.now().strftime("%Y%m%d%H")
|
|
65
|
+
cache_file = Path("./artifacts") / Path(f"tenable_assets_{self.plan_id}_{current_datetime}.json")
|
|
66
|
+
|
|
67
|
+
if (
|
|
68
|
+
cache_file.exists()
|
|
69
|
+
and (datetime.datetime.now() - datetime.datetime.fromtimestamp(cache_file.stat().st_mtime)).days < 1
|
|
70
|
+
and not self.is_empty(cache_file)
|
|
71
|
+
):
|
|
72
|
+
logger.info("Loading assets from cache...")
|
|
73
|
+
else:
|
|
74
|
+
self.authenticate()
|
|
75
|
+
logger.info("Fetching Tenable assets...")
|
|
76
|
+
|
|
77
|
+
if not self.client:
|
|
78
|
+
raise ValueError("Client not authenticated")
|
|
79
|
+
|
|
80
|
+
tenable_last_updated = self.get_last_update_time()
|
|
81
|
+
assets_iterator = (
|
|
82
|
+
self.client.exports.assets(updated_at=int(tenable_last_updated.timestamp()), tags=tags)
|
|
83
|
+
if tags
|
|
84
|
+
else self.client.exports.assets(updated_at=int(tenable_last_updated.timestamp()))
|
|
85
|
+
)
|
|
86
|
+
i = 0
|
|
87
|
+
with open(cache_file, "w") as f:
|
|
88
|
+
try:
|
|
89
|
+
for i, asset in enumerate(assets_iterator, 1):
|
|
90
|
+
f.write(json.dumps(asset) + "\n")
|
|
91
|
+
if i % 100 == 0:
|
|
92
|
+
logger.info(f"Fetched {i} assets")
|
|
93
|
+
except TioExportsError as e:
|
|
94
|
+
logger.error("Error fetching Tenable assets: %s", str(e))
|
|
95
|
+
|
|
96
|
+
logger.info(f"Total assets fetched: {i}")
|
|
97
|
+
|
|
98
|
+
# Count the number of lines in the file using linecache
|
|
99
|
+
self.num_assets_to_process = len(linecache.getlines(str(cache_file)))
|
|
100
|
+
logger.info(f"Total assets to process: {self.num_assets_to_process}")
|
|
101
|
+
|
|
102
|
+
# Process the assets
|
|
103
|
+
with open(cache_file, "r") as f:
|
|
104
|
+
for line in f:
|
|
105
|
+
asset = json.loads(line)
|
|
106
|
+
parsed_asset = self.parse_asset(asset)
|
|
107
|
+
yield parsed_asset
|
|
108
|
+
|
|
109
|
+
def get_last_update_time(self) -> datetime.datetime:
|
|
110
|
+
"""
|
|
111
|
+
Get the last update time for Tenable assets.
|
|
112
|
+
|
|
113
|
+
:return: The last update time
|
|
114
|
+
:rtype: datetime.datetime
|
|
115
|
+
"""
|
|
116
|
+
existing_assets: List[regscale_models.Asset] = regscale_models.Asset.get_all_by_parent(
|
|
117
|
+
parent_id=self.plan_id, parent_module="securityplans"
|
|
118
|
+
)
|
|
119
|
+
filtered_assets = [asset for asset in existing_assets if asset.tenableId and asset.dateLastUpdated]
|
|
120
|
+
|
|
121
|
+
if not filtered_assets:
|
|
122
|
+
return datetime.datetime.fromtimestamp(0)
|
|
123
|
+
|
|
124
|
+
return max(
|
|
125
|
+
datetime_obj(asset.dateLastUpdated) or datetime.datetime.fromtimestamp(0)
|
|
126
|
+
for asset in filtered_assets
|
|
127
|
+
if datetime_obj(asset.dateLastUpdated)
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
def parse_asset(self, node: Dict[str, Any]) -> IntegrationAsset:
|
|
131
|
+
"""
|
|
132
|
+
Parses Tenable assets
|
|
133
|
+
|
|
134
|
+
:param Dict[str, Any] node: The Tenable asset to parse
|
|
135
|
+
:return: The parsed IntegrationAsset
|
|
136
|
+
:rtype: IntegrationAsset
|
|
137
|
+
"""
|
|
138
|
+
system_types = node.get("system_types", [])
|
|
139
|
+
tenable_asset_type = system_types[0] if system_types else None
|
|
140
|
+
asset_type = self.map_tenable_to_regscale_asset_type(tenable_asset_type)
|
|
141
|
+
|
|
142
|
+
software_inventory = node.get("installed_software", [])
|
|
143
|
+
if software_inventory and isinstance(software_inventory[0], str):
|
|
144
|
+
software_inventory = [{"name": sw} for sw in software_inventory]
|
|
145
|
+
|
|
146
|
+
asset_id = node.get("id", "")
|
|
147
|
+
ipv4 = node.get("ipv4", "")
|
|
148
|
+
fqdn = node.get("fqdn", "")
|
|
149
|
+
if not asset_id:
|
|
150
|
+
asset_id = fqdn or ipv4
|
|
151
|
+
|
|
152
|
+
return IntegrationAsset(
|
|
153
|
+
name=self.get_asset_name(node),
|
|
154
|
+
external_id=node.get("uuid", ""),
|
|
155
|
+
identifier=asset_id,
|
|
156
|
+
asset_type=asset_type,
|
|
157
|
+
asset_owner_id=ScannerVariables.userId,
|
|
158
|
+
parent_id=self.plan_id,
|
|
159
|
+
parent_module=regscale_models.SecurityPlan.get_module_slug(),
|
|
160
|
+
asset_category=self.map_asset_category(tenable_asset_type),
|
|
161
|
+
date_last_updated=node.get("last_seen", ""),
|
|
162
|
+
status=self.map_tenable_status(node.get("terminated_at")),
|
|
163
|
+
ip_address=self.get_all_ip_addresses(node),
|
|
164
|
+
mac_address=self.get_all_mac_addresses(node),
|
|
165
|
+
fqdn=", ".join(node.get("fqdns", [])),
|
|
166
|
+
component_names=node.get("agent_names", []),
|
|
167
|
+
operating_system=", ".join(node.get("operating_systems", [])),
|
|
168
|
+
serial_number=node.get("bios_uuid", ""),
|
|
169
|
+
notes=self.generate_notes(node),
|
|
170
|
+
source_data=node,
|
|
171
|
+
software_inventory=software_inventory,
|
|
172
|
+
azure_identifier=node.get("azure_vm_id") or node.get("azure_resource_id", ""),
|
|
173
|
+
aws_identifier=node.get("aws_ec2_instance_id", ""),
|
|
174
|
+
google_identifier=node.get("gcp_instance_id", ""),
|
|
175
|
+
other_cloud_identifier=self.get_other_cloud_identifier(node),
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
@staticmethod
|
|
179
|
+
def get_asset_name(node: Dict[str, Any]) -> str:
|
|
180
|
+
"""
|
|
181
|
+
Get the asset name from various possible sources
|
|
182
|
+
|
|
183
|
+
:param Dict[str, Any] node: The Tenable asset data
|
|
184
|
+
:return: The asset name
|
|
185
|
+
:rtype: str
|
|
186
|
+
"""
|
|
187
|
+
for key in ["hostnames", "fqdns", "netbios_names", "ipv4s"]:
|
|
188
|
+
values = node.get(key, [])
|
|
189
|
+
if values and values[0]:
|
|
190
|
+
return values[0]
|
|
191
|
+
return "Unknown Asset"
|
|
192
|
+
|
|
193
|
+
@staticmethod
|
|
194
|
+
def _build_limited_string(items: set, char_limit: int = 450, tag: str = "Items") -> str:
|
|
195
|
+
"""
|
|
196
|
+
Build a comma-separated string from items, respecting a character limit.
|
|
197
|
+
|
|
198
|
+
:param set items: Set of items to join
|
|
199
|
+
:param int char_limit: Maximum characters allowed (default: 450)
|
|
200
|
+
:return: Comma-separated string of items
|
|
201
|
+
:rtype: str
|
|
202
|
+
"""
|
|
203
|
+
result = ""
|
|
204
|
+
for i, item in enumerate(items):
|
|
205
|
+
next_addition = item if i == 0 else ", " + item
|
|
206
|
+
if len(result) + len(next_addition) <= char_limit:
|
|
207
|
+
result += next_addition
|
|
208
|
+
else:
|
|
209
|
+
logger.warning("%s exceed character limit", tag)
|
|
210
|
+
break
|
|
211
|
+
return result
|
|
212
|
+
|
|
213
|
+
@staticmethod
|
|
214
|
+
def get_all_ip_addresses(ipv_node: Dict[str, Any]) -> str:
|
|
215
|
+
"""
|
|
216
|
+
Get all IP addresses from both IPv4 and IPv6 nodes
|
|
217
|
+
|
|
218
|
+
:param Dict[str, Any] ipv_node: The IPv4 node
|
|
219
|
+
:return: Comma-separated string of IP addresses
|
|
220
|
+
:rtype: str
|
|
221
|
+
"""
|
|
222
|
+
ip_addresses = set()
|
|
223
|
+
ip_addresses.update(ipv_node.get("ipv4s", []))
|
|
224
|
+
ip_addresses.update(ipv_node.get("ipv6s", []))
|
|
225
|
+
return TenableIntegration._build_limited_string(tag="IP Addresses", items=ip_addresses)
|
|
226
|
+
|
|
227
|
+
@staticmethod
|
|
228
|
+
def get_all_mac_addresses(node: Dict[str, Any]) -> str:
|
|
229
|
+
"""
|
|
230
|
+
Get all MAC addresses from all network interfaces
|
|
231
|
+
|
|
232
|
+
:param Dict[str, Any] node: The Tenable asset data
|
|
233
|
+
:return: Comma-separated string of MAC addresses
|
|
234
|
+
:rtype: str
|
|
235
|
+
"""
|
|
236
|
+
mac_addresses = set()
|
|
237
|
+
for interface in node.get("network_interfaces", []):
|
|
238
|
+
mac_addresses.update(interface.get("mac_addresses", []))
|
|
239
|
+
return TenableIntegration._build_limited_string(tag="MAC Addresses", items=mac_addresses)
|
|
240
|
+
|
|
241
|
+
@staticmethod
|
|
242
|
+
def generate_notes(node: Dict[str, Any]) -> str:
|
|
243
|
+
"""
|
|
244
|
+
Generate notes from Tenable asset data
|
|
245
|
+
|
|
246
|
+
:param Dict[str, Any] node: The Tenable asset data
|
|
247
|
+
:return: Generated notes
|
|
248
|
+
:rtype: str
|
|
249
|
+
"""
|
|
250
|
+
notes = []
|
|
251
|
+
if node.get("network_name"):
|
|
252
|
+
notes.append(f"Network: {node.get('network_name')}")
|
|
253
|
+
if node.get("acr_score") is not None:
|
|
254
|
+
notes.append(f"ACR Score: {node.get('acr_score')}")
|
|
255
|
+
if node.get("exposure_score") is not None:
|
|
256
|
+
notes.append(f"Exposure Score: {node.get('exposure_score')}")
|
|
257
|
+
if node.get("tags"):
|
|
258
|
+
tag_str = "; ".join([f"{tag.get('key', '')}: {tag.get('value', '')}" for tag in node.get("tags", [])])
|
|
259
|
+
notes.append(f"Tags: {tag_str}")
|
|
260
|
+
if node.get("sources"):
|
|
261
|
+
sources_str = "; ".join(
|
|
262
|
+
[
|
|
263
|
+
f"{source.get('name', '')} (First seen: {source.get('first_seen', '')}, Last seen: {source.get('last_seen', '')})"
|
|
264
|
+
for source in node.get("sources", [])
|
|
265
|
+
]
|
|
266
|
+
)
|
|
267
|
+
notes.append(f"Sources: {sources_str}")
|
|
268
|
+
return "\n".join(notes)
|
|
269
|
+
|
|
270
|
+
@staticmethod
|
|
271
|
+
def get_other_cloud_identifier(node: Dict[str, Any]) -> Optional[str]:
|
|
272
|
+
"""
|
|
273
|
+
Get other cloud identifier if present
|
|
274
|
+
|
|
275
|
+
:param Dict[str, Any] node: The Tenable asset data
|
|
276
|
+
:return: Other cloud identifier if present, None otherwise
|
|
277
|
+
:rtype: Optional[str]
|
|
278
|
+
"""
|
|
279
|
+
if node.get("gcp_project_id"):
|
|
280
|
+
return f"GCP Project: {node.get('gcp_project_id')}"
|
|
281
|
+
if node.get("aws_vpc_id"):
|
|
282
|
+
return f"AWS VPC: {node.get('aws_vpc_id')}"
|
|
283
|
+
return None
|
|
284
|
+
|
|
285
|
+
@staticmethod
|
|
286
|
+
def map_asset_category(tenable_type: Optional[str]) -> str:
|
|
287
|
+
"""
|
|
288
|
+
Map Tenable asset type to RegScale asset category.
|
|
289
|
+
|
|
290
|
+
:param Optional[str] tenable_type: The Tenable asset type
|
|
291
|
+
:return: Mapped asset category (either "Software" or "Hardware")
|
|
292
|
+
:rtype: regscale_models.AssetCategory
|
|
293
|
+
"""
|
|
294
|
+
if not tenable_type:
|
|
295
|
+
return regscale_models.AssetCategory.Hardware # Default to Hardware if type is unknown
|
|
296
|
+
|
|
297
|
+
# List of types that are typically considered software
|
|
298
|
+
software_types = ["application"]
|
|
299
|
+
|
|
300
|
+
return (
|
|
301
|
+
regscale_models.AssetCategory.Software
|
|
302
|
+
if tenable_type.lower() in software_types
|
|
303
|
+
else regscale_models.AssetCategory.Hardware
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
@staticmethod
|
|
307
|
+
def map_tenable_status(terminated_at: Optional[str]) -> str:
|
|
308
|
+
"""
|
|
309
|
+
Map Tenable status to IntegrationAsset status
|
|
310
|
+
|
|
311
|
+
:param Optional[str] terminated_at: The terminated_at value from Tenable
|
|
312
|
+
:return: Mapped status
|
|
313
|
+
:rtype: str
|
|
314
|
+
"""
|
|
315
|
+
return "Off-Network" if terminated_at else "Active (On Network)"
|
|
316
|
+
|
|
317
|
+
@staticmethod
|
|
318
|
+
def map_tenable_to_regscale_asset_type(tenable_type: Optional[str]) -> regscale_models.AssetType:
|
|
319
|
+
"""
|
|
320
|
+
Map Tenable asset type to RegScale AssetType enum.
|
|
321
|
+
|
|
322
|
+
:param Optional[str] tenable_type: The Tenable asset type
|
|
323
|
+
:return: Mapped RegScale AssetType
|
|
324
|
+
:rtype: regscale_models.AssetType
|
|
325
|
+
"""
|
|
326
|
+
if not tenable_type:
|
|
327
|
+
return regscale_models.AssetType.Other
|
|
328
|
+
|
|
329
|
+
tenable_to_regscale_map = {
|
|
330
|
+
"general-purpose": regscale_models.AssetType.Desktop,
|
|
331
|
+
"laptop": regscale_models.AssetType.Laptop,
|
|
332
|
+
"server": regscale_models.AssetType.PhysicalServer,
|
|
333
|
+
"hypervisor": regscale_models.AssetType.VM,
|
|
334
|
+
"mobile": regscale_models.AssetType.Phone,
|
|
335
|
+
"network": regscale_models.AssetType.NetworkRouter,
|
|
336
|
+
"firewall": regscale_models.AssetType.Firewall,
|
|
337
|
+
"tablet": regscale_models.AssetType.Tablet,
|
|
338
|
+
"switch": regscale_models.AssetType.NetworkSwitch,
|
|
339
|
+
"appliance": regscale_models.AssetType.Appliance,
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return tenable_to_regscale_map.get(tenable_type.lower(), regscale_models.AssetType.Other)
|
|
343
|
+
|
|
344
|
+
def fetch_findings(self, *args: Any, **kwargs: Any) -> Iterator[IntegrationFinding]:
|
|
345
|
+
"""
|
|
346
|
+
Fetches Tenable findings using the Tenable.io API
|
|
347
|
+
|
|
348
|
+
:yields: Iterator[IntegrationFinding]
|
|
349
|
+
"""
|
|
350
|
+
plan_id: int = int(kwargs.get("plan_id", self.plan_id))
|
|
351
|
+
tags: List[Tuple[str, str]] = kwargs.get("tags", [])
|
|
352
|
+
Path.mkdir(Path("./artifacts"), exist_ok=True, parents=True)
|
|
353
|
+
current_datetime = datetime.datetime.now().strftime("%Y%m%d%H")
|
|
354
|
+
cache_file = Path("./artifacts") / Path(f"tenable_findings_{self.plan_id}_{current_datetime}.json")
|
|
355
|
+
|
|
356
|
+
if (
|
|
357
|
+
cache_file.exists()
|
|
358
|
+
and (datetime.datetime.now() - datetime.datetime.fromtimestamp(cache_file.stat().st_mtime)).days < 1
|
|
359
|
+
and not self.is_empty(cache_file)
|
|
360
|
+
):
|
|
361
|
+
logger.info("Loading findings from cache...")
|
|
362
|
+
else:
|
|
363
|
+
logger.info("Fetching findings from Tenable...")
|
|
364
|
+
minimum_severity = validate_nessus_severity(TenableVariables.tenableMinimumSeverityFilter)
|
|
365
|
+
cvss2_min = get_min_cvss_score(minimum_severity)
|
|
366
|
+
latest_scan: int = get_last_pull_epoch(regscale_ssp_id=plan_id)
|
|
367
|
+
logger.info("Latest scan: %s", datetime.datetime.fromtimestamp(latest_scan))
|
|
368
|
+
self.authenticate()
|
|
369
|
+
if not self.client:
|
|
370
|
+
raise ValueError("Client not authenticated")
|
|
371
|
+
with open(cache_file, "w", encoding="utf-8") as f:
|
|
372
|
+
findings_iterator = (
|
|
373
|
+
self.client.exports.vulns(
|
|
374
|
+
vpr_score={"gte": cvss2_min}, since=latest_scan, state=["OPEN", "REOPENED"], tags=tags
|
|
375
|
+
)
|
|
376
|
+
if tags
|
|
377
|
+
else self.client.exports.vulns(
|
|
378
|
+
vpr_score={"gte": cvss2_min}, since=latest_scan, state=["OPEN", "REOPENED"]
|
|
379
|
+
)
|
|
380
|
+
)
|
|
381
|
+
for i, line in enumerate(findings_iterator):
|
|
382
|
+
f.write(json.dumps(line) + "\n")
|
|
383
|
+
if i % 100 == 0:
|
|
384
|
+
logger.info(f"Fetched {i} vulnerabilities")
|
|
385
|
+
|
|
386
|
+
# Count the number of lines in the file using linecache
|
|
387
|
+
self.num_findings_to_process = len(linecache.getlines(str(cache_file)))
|
|
388
|
+
logger.info(f"Total findings to process: {self.num_findings_to_process}")
|
|
389
|
+
with open(cache_file, "r") as f:
|
|
390
|
+
for line in f:
|
|
391
|
+
parsed_asset = self.parse_finding(json.loads(line))
|
|
392
|
+
if parsed_asset:
|
|
393
|
+
yield parsed_asset
|
|
394
|
+
|
|
395
|
+
def parse_finding(self, vuln: Dict[str, Any]) -> Optional[IntegrationFinding]:
|
|
396
|
+
"""
|
|
397
|
+
Parses a Tenable vulnerability into an IntegrationFinding object.
|
|
398
|
+
|
|
399
|
+
:param Dict[str, Any] vuln: The Tenable vulnerability to parse
|
|
400
|
+
:return: The parsed IntegrationFinding or None if parsing fails
|
|
401
|
+
:rtype: Optional[IntegrationFinding]
|
|
402
|
+
"""
|
|
403
|
+
from regscale.core.app.application import Application
|
|
404
|
+
|
|
405
|
+
app = Application()
|
|
406
|
+
|
|
407
|
+
try:
|
|
408
|
+
# Extract relevant data from the vulnerability dict
|
|
409
|
+
asset = vuln.get("asset", {})
|
|
410
|
+
plugin = vuln.get("plugin", {})
|
|
411
|
+
plugin_output = vuln.get("output", "")
|
|
412
|
+
is_stig = "xccdf_mil.disa.stig_rule" in plugin_output
|
|
413
|
+
plugin_name = plugin.get("name", [])[0] if isinstance(plugin.get("name"), list) else plugin.get("name")
|
|
414
|
+
|
|
415
|
+
severity = vuln.get("severity", "info").lower()
|
|
416
|
+
severity_id = vuln.get("severity_id", 0)
|
|
417
|
+
|
|
418
|
+
# Determine if this is an informational item or a vulnerability
|
|
419
|
+
is_informational = severity == "info" or severity_id == 0
|
|
420
|
+
|
|
421
|
+
if is_informational and not is_stig:
|
|
422
|
+
logger.info(f"Ignoring Informational Vulnerability {plugin_name}")
|
|
423
|
+
return None
|
|
424
|
+
|
|
425
|
+
category = f"Tenable Vulnerability: {plugin.get('family', 'General')}"
|
|
426
|
+
issue_type = "Vulnerability"
|
|
427
|
+
severity_default = app.config.get("vulnerabilityMappingDefault", regscale_models.IssueSeverity.NotAssigned)
|
|
428
|
+
severity = self.finding_severity_map.get(severity, severity_default)
|
|
429
|
+
status = (
|
|
430
|
+
regscale_models.IssueStatus.Open if vuln.get("state") == "OPEN" else regscale_models.IssueStatus.Closed
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
# Mapping for severity strings to integers
|
|
434
|
+
severity_map = {"critical": 4, "high": 3, "medium": 2, "low": 1, "info": 0}
|
|
435
|
+
severity_int = severity_map.get(severity, 0)
|
|
436
|
+
|
|
437
|
+
identifier = plugin.get("cve", []) or [plugin.get("name", "")]
|
|
438
|
+
identifier = identifier[0] if isinstance(identifier, list) else identifier
|
|
439
|
+
asset_id = asset.get("uuid", "")
|
|
440
|
+
ipv4 = asset.get("ipv4", "")
|
|
441
|
+
fqdn = asset.get("fqdn", "")
|
|
442
|
+
if not asset_id:
|
|
443
|
+
asset_id = fqdn or ipv4
|
|
444
|
+
|
|
445
|
+
cve = plugin.get("cve", [])[0] if isinstance(plugin.get("cve"), list) else None
|
|
446
|
+
|
|
447
|
+
integration_finding = IntegrationFinding(
|
|
448
|
+
control_labels=[],
|
|
449
|
+
category=category,
|
|
450
|
+
title=f"{identifier}: {plugin.get('name', '')}",
|
|
451
|
+
issue_title=f"{identifier}: {plugin.get('name', '')}",
|
|
452
|
+
description=plugin.get("description", ""),
|
|
453
|
+
severity=severity,
|
|
454
|
+
status=status,
|
|
455
|
+
asset_identifier=asset_id,
|
|
456
|
+
external_id=str(plugin.get("id", "")),
|
|
457
|
+
first_seen=vuln.get("first_found", get_current_datetime()),
|
|
458
|
+
last_seen=vuln.get("last_found", get_current_datetime()),
|
|
459
|
+
remediation=plugin.get("solution", ""),
|
|
460
|
+
cvss_score=float(plugin.get("cvss3_base_score") or plugin.get("cvss_base_score") or 0),
|
|
461
|
+
cve=cve,
|
|
462
|
+
vulnerability_type=self.title,
|
|
463
|
+
plugin_id=str(plugin.get("id", "")),
|
|
464
|
+
plugin_name=plugin_name,
|
|
465
|
+
ip_address=asset.get("ipv4", ""),
|
|
466
|
+
dns=asset.get("fqdn", ""),
|
|
467
|
+
severity_int=severity_int,
|
|
468
|
+
issue_type=issue_type,
|
|
469
|
+
date_created=get_current_datetime(),
|
|
470
|
+
date_last_updated=get_current_datetime(),
|
|
471
|
+
gaps="",
|
|
472
|
+
observations=vuln.get("output", ""),
|
|
473
|
+
evidence=vuln.get("output", ""),
|
|
474
|
+
identified_risk=plugin.get("risk_factor", ""),
|
|
475
|
+
impact="",
|
|
476
|
+
recommendation_for_mitigation=plugin.get("solution", ""),
|
|
477
|
+
rule_id=str(plugin.get("id", "")),
|
|
478
|
+
rule_version=plugin.get("version", ""),
|
|
479
|
+
results=vuln.get("output", ""),
|
|
480
|
+
comments=None,
|
|
481
|
+
baseline="",
|
|
482
|
+
poam_comments=None,
|
|
483
|
+
vulnerable_asset=asset_id,
|
|
484
|
+
source_rule_id=str(plugin.get("id", "")),
|
|
485
|
+
)
|
|
486
|
+
if is_stig:
|
|
487
|
+
integration_finding = parse_stig_output(output=plugin_output, finding=integration_finding)
|
|
488
|
+
return integration_finding
|
|
489
|
+
except Exception as e:
|
|
490
|
+
logger.error("Error parsing Tenable finding: %s", str(e), exc_info=True)
|
|
491
|
+
return None
|
|
492
|
+
|
|
493
|
+
def is_empty(self, file_path: Path) -> bool:
|
|
494
|
+
"""
|
|
495
|
+
Check if the file is empty.
|
|
496
|
+
|
|
497
|
+
:param Path file_path: The path to the file
|
|
498
|
+
:return: True if the file is empty, False otherwise
|
|
499
|
+
:rtype: bool
|
|
500
|
+
"""
|
|
501
|
+
try:
|
|
502
|
+
return file_path.stat().st_size == 0
|
|
503
|
+
except FileNotFoundError:
|
|
504
|
+
return True
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""Functions for parsing STIG output from Tenable"""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import re
|
|
5
|
+
from typing import Union
|
|
6
|
+
|
|
7
|
+
from regscale.core.app.utils.app_utils import get_current_datetime
|
|
8
|
+
from regscale.integrations.scanner_integration import IntegrationFinding, issue_due_date
|
|
9
|
+
from regscale.models import regscale_models
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def parse_stig_output(output: str, finding: IntegrationFinding) -> IntegrationFinding:
|
|
15
|
+
"""
|
|
16
|
+
Parses STIG output and constructs a finding dictionary matching IntegrationFinding.
|
|
17
|
+
|
|
18
|
+
:param str output: The STIG output string to parse.
|
|
19
|
+
:param IntegrationFinding finding: The finding to update.
|
|
20
|
+
:return: An IntegrationFinding object containing the parsed finding information.
|
|
21
|
+
:rtype: IntegrationFinding
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def _extract_field(pattern: str, text: str, flags: Union[int, re.RegexFlag] = 0, group: int = 1) -> str:
|
|
25
|
+
"""
|
|
26
|
+
Extracts a field from a string using a regular expression
|
|
27
|
+
|
|
28
|
+
:param str pattern: The regular expression pattern to search for
|
|
29
|
+
:param str text: The string to search in
|
|
30
|
+
:param int flags: Optional regular expression flags, defaults to 0
|
|
31
|
+
:param int group: The group number to return from the match, defaults to 1
|
|
32
|
+
:return: The extracted field as a string. Empty string if no match was found
|
|
33
|
+
:rtype: str
|
|
34
|
+
"""
|
|
35
|
+
match = re.search(pattern, text, flags)
|
|
36
|
+
return match.group(group).strip() if match else ""
|
|
37
|
+
|
|
38
|
+
# Extract fields
|
|
39
|
+
check_name_full = _extract_field(r"Check Name:\s*(.*?)\n", output, flags=re.DOTALL | re.MULTILINE)
|
|
40
|
+
check_name_parts = check_name_full.split(":", 1)
|
|
41
|
+
rule_id = check_name_parts[0].strip()
|
|
42
|
+
check_description = check_name_parts[1].strip() if len(check_name_parts) > 1 else ""
|
|
43
|
+
|
|
44
|
+
baseline = _extract_field(r"(.*?)\s+Target\s+(.*)", check_description, group=2)
|
|
45
|
+
if target_match := _extract_field(r"(.*?)\s+Target\s+(.*)", check_description):
|
|
46
|
+
check_description = check_description[: check_description.find(target_match) + len(target_match)].strip()
|
|
47
|
+
|
|
48
|
+
information = _extract_field(r"Information:\s*(.*?)\n", output, flags=re.DOTALL | re.MULTILINE)
|
|
49
|
+
vuln_discuss = _extract_field(r"VulnDiscussion='(.*?)'\s", output, flags=re.DOTALL | re.MULTILINE)
|
|
50
|
+
result = _extract_field(r"Result:\s*(.*?)(?:\n|$)", output, flags=re.IGNORECASE | re.DOTALL)
|
|
51
|
+
solution = _extract_field(r"Solution:\s*(.*?)\n\nReference Information:", output, flags=re.DOTALL | re.MULTILINE)
|
|
52
|
+
|
|
53
|
+
# Extract reference information
|
|
54
|
+
ref_info = _extract_field(r"Reference Information:\s*(.*)", output, flags=re.DOTALL | re.MULTILINE)
|
|
55
|
+
ref_dict = dict(item.split("|", 1) for item in ref_info.split(",") if "|" in item)
|
|
56
|
+
|
|
57
|
+
# Extract specific references
|
|
58
|
+
cci_ref = ref_dict.get("CCI", "CCI-000366")
|
|
59
|
+
severity = ref_dict.get("SEVERITY", "").lower()
|
|
60
|
+
oval_def = ref_dict.get("OVAL-DEF", "")
|
|
61
|
+
generated_date = ref_dict.get("GENERATED-DATE", "")
|
|
62
|
+
updated_date = ref_dict.get("UPDATED-DATE", "")
|
|
63
|
+
scan_date = ref_dict.get("SCAN-DATE", "")
|
|
64
|
+
rule_id_full = ref_dict.get("RULE-ID", "")
|
|
65
|
+
group_id = ref_dict.get("GROUP-ID", "")
|
|
66
|
+
|
|
67
|
+
vuln_num_match = re.search(r"SV-\d+r\d+_rule", rule_id)
|
|
68
|
+
vuln_num = vuln_num_match.group(0) if vuln_num_match else "unknown"
|
|
69
|
+
|
|
70
|
+
title = f"{vuln_num}: {check_description}"
|
|
71
|
+
issue_title = title
|
|
72
|
+
|
|
73
|
+
status_map = {
|
|
74
|
+
"PASSED": regscale_models.ChecklistStatus.PASS,
|
|
75
|
+
"FAILED": regscale_models.ChecklistStatus.FAIL,
|
|
76
|
+
"ERROR": regscale_models.ChecklistStatus.FAIL,
|
|
77
|
+
"NOT APPLICABLE": regscale_models.ChecklistStatus.NOT_APPLICABLE,
|
|
78
|
+
"NOT_APPLICABLE": regscale_models.ChecklistStatus.NOT_APPLICABLE,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
result_key = result.upper().replace("_", " ").strip()
|
|
82
|
+
if result_key not in status_map:
|
|
83
|
+
logger.warning(f"Result '{result}' not found in status map")
|
|
84
|
+
status = regscale_models.ChecklistStatus.NOT_REVIEWED
|
|
85
|
+
else:
|
|
86
|
+
status = status_map[result_key]
|
|
87
|
+
|
|
88
|
+
# Map severity to IssueSeverity enum
|
|
89
|
+
priority = (severity or "").title()
|
|
90
|
+
severity_map = {
|
|
91
|
+
"critical": regscale_models.IssueSeverity.Critical,
|
|
92
|
+
"high": regscale_models.IssueSeverity.High,
|
|
93
|
+
"medium": regscale_models.IssueSeverity.Moderate,
|
|
94
|
+
"low": regscale_models.IssueSeverity.Low,
|
|
95
|
+
}
|
|
96
|
+
severity = severity_map.get(severity.lower(), regscale_models.IssueSeverity.NotAssigned)
|
|
97
|
+
|
|
98
|
+
results = (
|
|
99
|
+
f"Vulnerability Number: {vuln_num}, Severity: {severity.value}, "
|
|
100
|
+
f"Rule Title: {check_description}<br><br>"
|
|
101
|
+
f"Check Content: {information}<br><br>"
|
|
102
|
+
f"Vulnerability Discussion: {vuln_discuss}<br><br>"
|
|
103
|
+
f"Fix Text: {solution}<br><br>"
|
|
104
|
+
f"STIG Reference: {rule_id}"
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
current_datetime = get_current_datetime()
|
|
108
|
+
finding.title = title
|
|
109
|
+
finding.category = "STIG"
|
|
110
|
+
finding.plugin_id = cci_ref
|
|
111
|
+
finding.plugin_name = rule_id
|
|
112
|
+
finding.severity = severity
|
|
113
|
+
finding.description = f"{information}\n\nVulnerability Discussion: {vuln_discuss}\n\nSolution: {solution}"
|
|
114
|
+
finding.status = status
|
|
115
|
+
finding.priority = priority # Set priority based on severity
|
|
116
|
+
finding.first_seen = current_datetime
|
|
117
|
+
finding.last_seen = current_datetime
|
|
118
|
+
finding.issue_title = issue_title
|
|
119
|
+
finding.issue_type = "Risk"
|
|
120
|
+
finding.date_created = generated_date
|
|
121
|
+
finding.date_last_updated = updated_date
|
|
122
|
+
finding.due_date = issue_due_date(severity, generated_date)
|
|
123
|
+
finding.external_id = f"{cci_ref}:{vuln_num}:{finding.asset_identifier}"
|
|
124
|
+
finding.recommendation_for_mitigation = solution
|
|
125
|
+
finding.cci_ref = cci_ref
|
|
126
|
+
finding.rule_id = rule_id
|
|
127
|
+
finding.results = results
|
|
128
|
+
finding.baseline = baseline
|
|
129
|
+
finding.vulnerability_number = vuln_num
|
|
130
|
+
finding.oval_def = oval_def
|
|
131
|
+
finding.scan_date = scan_date
|
|
132
|
+
finding.rule_id_full = rule_id_full
|
|
133
|
+
finding.group_id = group_id
|
|
134
|
+
|
|
135
|
+
# Future values
|
|
136
|
+
# finding.comments = ""
|
|
137
|
+
# finding.poam_comments = ""
|
|
138
|
+
# finding.rule_version = ""
|
|
139
|
+
|
|
140
|
+
return finding
|