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,353 @@
|
|
|
1
|
+
"""
|
|
2
|
+
STIG Mapping Engine
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
from typing import Any, Dict, List, Optional
|
|
9
|
+
|
|
10
|
+
from regscale.models import SecurityPlan
|
|
11
|
+
from regscale.models.regscale_models import Asset, AssetMapping, Component
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class StigMappingEngine:
|
|
17
|
+
"""
|
|
18
|
+
A class to map assets to STIGs based on defined rules
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
comparator_functions = {
|
|
22
|
+
"equals": lambda a, b: a == b,
|
|
23
|
+
"contains": lambda a, b: b in a,
|
|
24
|
+
"notcontains": lambda a, b: b not in a,
|
|
25
|
+
"startswith": lambda a, b: a.startswith(b),
|
|
26
|
+
"notin": lambda a, b: b not in a,
|
|
27
|
+
"endswith": lambda a, b: a.endswith(b),
|
|
28
|
+
"notstartswith": lambda a, b: not a.startswith(b),
|
|
29
|
+
"notendswith": lambda a, b: not a.endswith(b),
|
|
30
|
+
"gt": lambda a, b: a > b,
|
|
31
|
+
"lt": lambda a, b: a < b,
|
|
32
|
+
"gte": lambda a, b: a >= b,
|
|
33
|
+
"lte": lambda a, b: a <= b,
|
|
34
|
+
"ne": lambda a, b: a != b,
|
|
35
|
+
"in": lambda a, b: a in b,
|
|
36
|
+
"nin": lambda a, b: a not in b,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
def __init__(self, json_file: str):
|
|
40
|
+
self._mapping_cache = None
|
|
41
|
+
self._component_cache = None
|
|
42
|
+
self.rules = self.load_rules(json_file)
|
|
43
|
+
logger.info(f"Loaded {len(self.rules)} rules from {json_file}")
|
|
44
|
+
# Preprocess rules for faster access
|
|
45
|
+
self.stig_to_rules = {}
|
|
46
|
+
for rule in self.rules:
|
|
47
|
+
stig_name = rule.get("stig")
|
|
48
|
+
if stig_name not in self.stig_to_rules:
|
|
49
|
+
self.stig_to_rules[stig_name] = []
|
|
50
|
+
self.stig_to_rules[stig_name].append(rule.get("comparators", []))
|
|
51
|
+
|
|
52
|
+
@staticmethod
|
|
53
|
+
def load_rules(json_file: str) -> List[Dict[str, str]]:
|
|
54
|
+
"""
|
|
55
|
+
Load rules from a JSON file
|
|
56
|
+
|
|
57
|
+
:param str json_file: The path to the JSON file
|
|
58
|
+
:return: A list of rules
|
|
59
|
+
:rtype: List[Dict[str, str]]
|
|
60
|
+
"""
|
|
61
|
+
if not os.path.exists(json_file):
|
|
62
|
+
logger.error(f"File not found: {json_file}")
|
|
63
|
+
return []
|
|
64
|
+
try:
|
|
65
|
+
with open(json_file, "r") as file:
|
|
66
|
+
data = json.load(file)
|
|
67
|
+
return data.get("rules", [])
|
|
68
|
+
except json.JSONDecodeError as e:
|
|
69
|
+
logger.error(f"JSON decoding error in file {json_file}: {e}")
|
|
70
|
+
except Exception as e:
|
|
71
|
+
logger.error(f"Error loading rules from {json_file}: {e}")
|
|
72
|
+
return []
|
|
73
|
+
|
|
74
|
+
def evaluate_match(self, item: Any, comparators: List[Dict[str, str]]) -> bool:
|
|
75
|
+
"""
|
|
76
|
+
Evaluates a single item (Asset or software inventory item) against multiple comparator rules
|
|
77
|
+
|
|
78
|
+
:param Any item: The asset or software inventory item
|
|
79
|
+
:param List[Dict[str, str]] comparators: List of comparators
|
|
80
|
+
:return: True if item meets all 'and' comparators or at least one 'or' comparator, otherwise False
|
|
81
|
+
:rtype: bool
|
|
82
|
+
"""
|
|
83
|
+
# Separate comparators by their logical operator
|
|
84
|
+
and_comparators = [comp for comp in comparators if comp.get("logical_operator", "and").lower() == "and"]
|
|
85
|
+
or_comparators = [comp for comp in comparators if comp.get("logical_operator", "and").lower() == "or"]
|
|
86
|
+
|
|
87
|
+
# Evaluate 'and' comparators
|
|
88
|
+
if not all(self.evaluate_single_comparator(item, comp) for comp in and_comparators):
|
|
89
|
+
return False
|
|
90
|
+
|
|
91
|
+
# Evaluate 'or' comparators
|
|
92
|
+
if or_comparators:
|
|
93
|
+
return any(self.evaluate_single_comparator(item, comp) for comp in or_comparators)
|
|
94
|
+
|
|
95
|
+
# If all 'and' comparators passed and there are no 'or' comparators
|
|
96
|
+
return True
|
|
97
|
+
|
|
98
|
+
@staticmethod
|
|
99
|
+
def get_item_value(item: Any, property_name: Any) -> Optional[Any]:
|
|
100
|
+
"""
|
|
101
|
+
Fetches the property value from the item, or None if not found
|
|
102
|
+
|
|
103
|
+
:param Any item: The asset or software inventory item
|
|
104
|
+
:param Any property_name: The property name
|
|
105
|
+
:return: The property value
|
|
106
|
+
:rtype: Optional[Any]
|
|
107
|
+
"""
|
|
108
|
+
if isinstance(item, dict):
|
|
109
|
+
return item.get(property_name)
|
|
110
|
+
return getattr(item, property_name, None)
|
|
111
|
+
|
|
112
|
+
def evaluate_single_comparator(self, item: Any, comparator: dict) -> bool:
|
|
113
|
+
"""
|
|
114
|
+
Evaluates a single comparator against the item
|
|
115
|
+
|
|
116
|
+
:param Any item: The asset or software inventory item
|
|
117
|
+
:param Dict[str, str] comparator: The comparator
|
|
118
|
+
:return: True if the item satisfies the comparator, otherwise False
|
|
119
|
+
:rtype: bool
|
|
120
|
+
"""
|
|
121
|
+
property_name = comparator.get("property")
|
|
122
|
+
item_value = self.get_item_value(item, property_name)
|
|
123
|
+
|
|
124
|
+
if item_value is None:
|
|
125
|
+
return False
|
|
126
|
+
|
|
127
|
+
operator = comparator.get("comparator")
|
|
128
|
+
value = comparator.get("value")
|
|
129
|
+
comparator_func = StigMappingEngine.comparator_functions.get(operator)
|
|
130
|
+
|
|
131
|
+
return comparator_func(item_value, value) if comparator_func else False
|
|
132
|
+
|
|
133
|
+
@staticmethod
|
|
134
|
+
def find_matching_stigs(items: List[Dict[str, Any]], rules: List[Dict[str, Any]]) -> List[str]:
|
|
135
|
+
"""
|
|
136
|
+
Checks a list of items (software inventory or assets) to see which STIGs they match
|
|
137
|
+
|
|
138
|
+
:param List[Dict[str, Any]] items: List of items (e.g., software inventory dictionaries)
|
|
139
|
+
:param List[Dict[str, Any]] rules: List of STIG rules, each containing a "stig" name and comparators
|
|
140
|
+
:return: List of matched STIG names
|
|
141
|
+
:rtype: List[str]
|
|
142
|
+
"""
|
|
143
|
+
matched_stigs = []
|
|
144
|
+
|
|
145
|
+
for rule in rules:
|
|
146
|
+
stig_name = rule.get("stig")
|
|
147
|
+
comparators = rule.get("comparators", [])
|
|
148
|
+
|
|
149
|
+
# Track satisfaction of each comparator across all items
|
|
150
|
+
comparator_match = {i: False for i in range(len(comparators))}
|
|
151
|
+
|
|
152
|
+
# Go through each comparator and attempt to satisfy it with any item
|
|
153
|
+
for i, comparator in enumerate(comparators):
|
|
154
|
+
property_name = comparator.get("property")
|
|
155
|
+
value = comparator.get("value")
|
|
156
|
+
operator = comparator.get("comparator")
|
|
157
|
+
|
|
158
|
+
# Check if any item satisfies this comparator
|
|
159
|
+
for item in items:
|
|
160
|
+
item_value = item.get(property_name)
|
|
161
|
+
|
|
162
|
+
# Retrieve the comparison function
|
|
163
|
+
comparator_func = StigMappingEngine.comparator_functions.get(operator)
|
|
164
|
+
if comparator_func and comparator_func(item_value, value):
|
|
165
|
+
comparator_match[i] = True
|
|
166
|
+
break # Move to the next comparator once a match is found
|
|
167
|
+
|
|
168
|
+
# Evaluate final match based on logical operators
|
|
169
|
+
if all(comparator_match.values()):
|
|
170
|
+
matched_stigs.append(stig_name)
|
|
171
|
+
|
|
172
|
+
return matched_stigs
|
|
173
|
+
|
|
174
|
+
@staticmethod
|
|
175
|
+
def asset_matches_comparators(asset: Asset, comparators: List[Dict[str, str]]) -> bool:
|
|
176
|
+
"""
|
|
177
|
+
Determine if the asset matches the given comparators
|
|
178
|
+
|
|
179
|
+
:param Asset asset: An asset
|
|
180
|
+
:param List[Dict[str, str]] comparators: List of comparator dictionaries
|
|
181
|
+
:return: True if the asset matches the comparators, False otherwise
|
|
182
|
+
:rtype: bool
|
|
183
|
+
"""
|
|
184
|
+
match_result = True
|
|
185
|
+
|
|
186
|
+
for comparator in comparators:
|
|
187
|
+
property_name = comparator.get("property")
|
|
188
|
+
if not hasattr(asset, property_name):
|
|
189
|
+
return False
|
|
190
|
+
|
|
191
|
+
operator = comparator.get("comparator")
|
|
192
|
+
comparator_func = StigMappingEngine.comparator_functions.get(operator)
|
|
193
|
+
if not comparator_func:
|
|
194
|
+
return False
|
|
195
|
+
|
|
196
|
+
value = comparator.get("value")
|
|
197
|
+
asset_value = getattr(asset, property_name)
|
|
198
|
+
comparison_result = comparator_func(asset_value, value)
|
|
199
|
+
|
|
200
|
+
logical_operator = comparator.get("logical_operator", "and").lower()
|
|
201
|
+
|
|
202
|
+
if logical_operator == "and":
|
|
203
|
+
match_result = match_result and comparison_result
|
|
204
|
+
if not match_result:
|
|
205
|
+
return False
|
|
206
|
+
elif logical_operator == "or":
|
|
207
|
+
match_result = match_result or comparison_result
|
|
208
|
+
else:
|
|
209
|
+
logger.warning(f"Unknown logical operator: {logical_operator}")
|
|
210
|
+
return False
|
|
211
|
+
|
|
212
|
+
return match_result
|
|
213
|
+
|
|
214
|
+
def match_asset_to_stigs(
|
|
215
|
+
self, asset: Asset, ssp_id: int, software_inventory: Optional[List] = None
|
|
216
|
+
) -> List[Component]:
|
|
217
|
+
"""
|
|
218
|
+
Match an asset to STIG components based on rules
|
|
219
|
+
|
|
220
|
+
:param Asset asset: An asset
|
|
221
|
+
:param int ssp_id: The security plan ID
|
|
222
|
+
:param Optional[List] software_inventory: A list of software inventory
|
|
223
|
+
:return: A list of matching components
|
|
224
|
+
:rtype: List[Component]
|
|
225
|
+
"""
|
|
226
|
+
if software_inventory is None:
|
|
227
|
+
software_inventory = []
|
|
228
|
+
if not self.rules:
|
|
229
|
+
return []
|
|
230
|
+
|
|
231
|
+
matching_components = []
|
|
232
|
+
|
|
233
|
+
# Ensure component cache is initialized
|
|
234
|
+
if self._component_cache is None:
|
|
235
|
+
self._component_cache = self.get_component_dict(ssp_id)
|
|
236
|
+
|
|
237
|
+
for stig_name, comparators_list in self.stig_to_rules.items():
|
|
238
|
+
component = self._component_cache.get(stig_name)
|
|
239
|
+
if not component:
|
|
240
|
+
continue
|
|
241
|
+
|
|
242
|
+
for comparators in comparators_list:
|
|
243
|
+
if self.asset_matches_comparators(asset, comparators):
|
|
244
|
+
matching_components.append(component)
|
|
245
|
+
break # No need to check other comparators for this STIG
|
|
246
|
+
|
|
247
|
+
return matching_components
|
|
248
|
+
|
|
249
|
+
def map_stigs_to_assets(
|
|
250
|
+
self,
|
|
251
|
+
assets: List[Asset],
|
|
252
|
+
ssp_id: int,
|
|
253
|
+
) -> List[AssetMapping]:
|
|
254
|
+
"""
|
|
255
|
+
Map STIG components to assets based on rules
|
|
256
|
+
|
|
257
|
+
:param List[Asset] asset_list assets: A list of assets
|
|
258
|
+
:param List[Component] ssp_id: The security plan ID
|
|
259
|
+
:return: A list of asset mappings
|
|
260
|
+
:rtype: List[AssetMapping]
|
|
261
|
+
"""
|
|
262
|
+
new_mappings = []
|
|
263
|
+
|
|
264
|
+
# Cache components to avoid redundant database queries
|
|
265
|
+
components = self.get_components(ssp_id)
|
|
266
|
+
|
|
267
|
+
# Build a mapping of existing mappings for quick lookup
|
|
268
|
+
existing_mappings = {}
|
|
269
|
+
for component in components:
|
|
270
|
+
mappings = AssetMapping.find_mappings(component_id=component.id)
|
|
271
|
+
existing_mappings[component.id] = {m.assetId for m in mappings}
|
|
272
|
+
|
|
273
|
+
for stig_name, comparators_list in self.stig_to_rules.items():
|
|
274
|
+
component = self._component_cache.get(stig_name)
|
|
275
|
+
if not component:
|
|
276
|
+
continue
|
|
277
|
+
|
|
278
|
+
component_existing_asset_ids = existing_mappings.get(component.id, set())
|
|
279
|
+
|
|
280
|
+
for asset in assets:
|
|
281
|
+
for comparators in comparators_list:
|
|
282
|
+
if self.asset_matches_comparators(asset, comparators):
|
|
283
|
+
if asset.id not in component_existing_asset_ids:
|
|
284
|
+
mapping = AssetMapping(assetId=asset.id, componentId=component.id)
|
|
285
|
+
new_mappings.append(mapping)
|
|
286
|
+
component_existing_asset_ids.add(asset.id)
|
|
287
|
+
logger.info(f"Mapping -> Asset ID: {asset.id}, Component ID: {component.id}")
|
|
288
|
+
else:
|
|
289
|
+
logger.info(
|
|
290
|
+
f"Existing mapping found for Asset ID: {asset.id}, Component ID: {component.id}"
|
|
291
|
+
)
|
|
292
|
+
break # No need to check other comparators for this asset and STIG
|
|
293
|
+
|
|
294
|
+
return new_mappings
|
|
295
|
+
|
|
296
|
+
def get_components(self, ssp_id: int) -> List[Component]:
|
|
297
|
+
"""
|
|
298
|
+
Get all components for the given security plan
|
|
299
|
+
|
|
300
|
+
:param int ssp_id: The security plan ID
|
|
301
|
+
:return: A list of components
|
|
302
|
+
:rtype: List[Component]
|
|
303
|
+
"""
|
|
304
|
+
if not hasattr(self, "_component_cache"):
|
|
305
|
+
components = Component.get_all_by_parent(parent_module=SecurityPlan.get_module_slug(), parent_id=ssp_id)
|
|
306
|
+
self._component_cache = {comp.title: comp for comp in components}
|
|
307
|
+
else:
|
|
308
|
+
components = self._component_cache.values()
|
|
309
|
+
return components
|
|
310
|
+
|
|
311
|
+
def get_component_dict(self, ssp_id: int) -> Dict[str, Component]:
|
|
312
|
+
"""
|
|
313
|
+
Get a dictionary of components for the given security plan
|
|
314
|
+
|
|
315
|
+
:param int ssp_id: The security plan ID
|
|
316
|
+
:return: A dictionary of components
|
|
317
|
+
:rtype: Dict[str, Component]
|
|
318
|
+
"""
|
|
319
|
+
if not hasattr(self, "_component_cache") or self._component_cache is None:
|
|
320
|
+
components = Component.get_all_by_parent(parent_module=SecurityPlan.get_module_slug(), parent_id=ssp_id)
|
|
321
|
+
self._component_cache = {comp.title: comp for comp in components}
|
|
322
|
+
return self._component_cache
|
|
323
|
+
|
|
324
|
+
def map_associated_stigs_to_asset(self, asset: Asset, ssp_id: int) -> List[AssetMapping]:
|
|
325
|
+
"""
|
|
326
|
+
Map associated STIGs to an asset based on rules
|
|
327
|
+
|
|
328
|
+
:param Asset asset: An asset
|
|
329
|
+
:param int ssp_id: The security plan ID
|
|
330
|
+
:return: A list of asset mappings
|
|
331
|
+
:rtype: List[AssetMapping]
|
|
332
|
+
"""
|
|
333
|
+
new_mappings = []
|
|
334
|
+
associated_components = self.match_asset_to_stigs(asset, ssp_id)
|
|
335
|
+
|
|
336
|
+
# Initialize or update the mapping cache
|
|
337
|
+
if not hasattr(self, "_mapping_cache") or self._mapping_cache is None:
|
|
338
|
+
mappings = AssetMapping.get_all_by_parent(parent_module=Asset.get_module_slug(), parent_id=asset.id)
|
|
339
|
+
self._mapping_cache = {m.componentId: m for m in mappings}
|
|
340
|
+
|
|
341
|
+
existing_component_ids = set(self._mapping_cache.keys())
|
|
342
|
+
|
|
343
|
+
for component in associated_components:
|
|
344
|
+
if component.id not in existing_component_ids:
|
|
345
|
+
mapping = AssetMapping(assetId=asset.id, componentId=component.id)
|
|
346
|
+
mapping.create()
|
|
347
|
+
new_mappings.append(mapping)
|
|
348
|
+
self._mapping_cache[component.id] = mapping
|
|
349
|
+
logger.debug(f"Created mapping for Asset ID: {asset.id}, Component ID: {component.id}")
|
|
350
|
+
else:
|
|
351
|
+
logger.debug(f"Mapping already exists for Asset ID: {asset.id}, Component ID: {component.id}")
|
|
352
|
+
|
|
353
|
+
return new_mappings
|
|
File without changes
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module is responsible for parsing STIG (Security Technical Implementation Guide) .ckl (Checklist) files.
|
|
3
|
+
It provides functionality to extract relevant information from .ckl files for further processing and analysis.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import io
|
|
7
|
+
import logging
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import List, Dict, Optional, Any, Generator, Union
|
|
11
|
+
|
|
12
|
+
import lxml.etree as et
|
|
13
|
+
from lxml.etree import Element
|
|
14
|
+
from pydantic import BaseModel, ConfigDict
|
|
15
|
+
|
|
16
|
+
from regscale.core.utils import snakify
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger("regscale")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def stig_component_title(stig_title: str) -> str:
|
|
22
|
+
"""
|
|
23
|
+
Extract the component title from the STIG title.
|
|
24
|
+
|
|
25
|
+
:param str stig_title: The STIG title.
|
|
26
|
+
:return: The component title.
|
|
27
|
+
:rtype: str
|
|
28
|
+
"""
|
|
29
|
+
return stig_title.replace(" ", " ").strip() # Ensure no double spaces are present
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def find_stig_files(directory: Path) -> List[Path]:
|
|
33
|
+
"""
|
|
34
|
+
Finds all STIG (.ckl) files within the specified directory.
|
|
35
|
+
|
|
36
|
+
:param Path directory: Path to the directory where STIG files are located.
|
|
37
|
+
:return: A list of paths to STIG files.
|
|
38
|
+
:rtype: List[Path]
|
|
39
|
+
"""
|
|
40
|
+
if str(directory).startswith("s3://"):
|
|
41
|
+
logger.info(f"Found S3 URI: {directory}")
|
|
42
|
+
return [directory]
|
|
43
|
+
|
|
44
|
+
if str(directory).endswith(".ckl"):
|
|
45
|
+
logger.info(f"Found CKL file: {directory}")
|
|
46
|
+
return [directory]
|
|
47
|
+
|
|
48
|
+
logger.info(f"Searching for STIG files in {directory}")
|
|
49
|
+
path = Path(directory)
|
|
50
|
+
|
|
51
|
+
if not path.is_dir():
|
|
52
|
+
logger.error(f"{directory} is not a valid directory")
|
|
53
|
+
return []
|
|
54
|
+
|
|
55
|
+
def is_hidden(filepath):
|
|
56
|
+
return any(part.startswith(".") for part in filepath.parts)
|
|
57
|
+
|
|
58
|
+
stig_files: list[Path] = [file for file in path.glob("**/*.ckl") if not is_hidden(file)]
|
|
59
|
+
logger.info(f"Found {len(stig_files)} STIG file(s)")
|
|
60
|
+
return stig_files
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class Asset(BaseModel):
|
|
64
|
+
"""Data model for Asset with optional attributes."""
|
|
65
|
+
|
|
66
|
+
model_config = ConfigDict(populate_by_name=True, alias_generator=snakify)
|
|
67
|
+
|
|
68
|
+
role: Optional[str] = None
|
|
69
|
+
asset_type: Optional[str] = None
|
|
70
|
+
host_name: Optional[str] = None
|
|
71
|
+
host_ip: Optional[str] = None
|
|
72
|
+
host_mac: Optional[str] = None
|
|
73
|
+
host_fqdn: Optional[str] = None
|
|
74
|
+
tech_area: Optional[str] = None
|
|
75
|
+
target_key: Optional[str] = None
|
|
76
|
+
web_or_database: Optional[bool] = None
|
|
77
|
+
web_db_site: Optional[str] = None
|
|
78
|
+
web_db_instance: Optional[str] = None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class STIGInfo(BaseModel):
|
|
82
|
+
"""Data model for STIG Information with optional and required attributes."""
|
|
83
|
+
|
|
84
|
+
version: str
|
|
85
|
+
classification: str
|
|
86
|
+
customname: Optional[str] = None
|
|
87
|
+
stigid: str
|
|
88
|
+
description: Optional[str] = None
|
|
89
|
+
filename: Optional[str] = None
|
|
90
|
+
releaseinfo: str
|
|
91
|
+
title: str
|
|
92
|
+
uuid: str
|
|
93
|
+
notice: str
|
|
94
|
+
source: Optional[str] = None
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class VulnStatus(Enum):
|
|
98
|
+
"""Enumeration for STIG vulnerability status."""
|
|
99
|
+
|
|
100
|
+
OPEN = "OPEN"
|
|
101
|
+
NOT_A_FINDING = "NOT A FINDING"
|
|
102
|
+
NOT_APPLICABLE = "NOT APPLICABLE"
|
|
103
|
+
COMPLETED = "COMPLETED"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class Vuln(BaseModel):
|
|
107
|
+
"""Data model for Vulnerability with optional and required attributes."""
|
|
108
|
+
|
|
109
|
+
vuln_num: str
|
|
110
|
+
severity: str
|
|
111
|
+
group_title: str
|
|
112
|
+
rule_id: str
|
|
113
|
+
rule_ver: str
|
|
114
|
+
rule_title: str
|
|
115
|
+
check_content: Optional[str] = None
|
|
116
|
+
fix_text: str
|
|
117
|
+
check_content_ref: Optional[str] = None
|
|
118
|
+
weight: str
|
|
119
|
+
stigref: Optional[str] = None
|
|
120
|
+
targetkey: Optional[str] = None
|
|
121
|
+
stig_uuid: str
|
|
122
|
+
vuln_discuss: Optional[str] = None
|
|
123
|
+
ia_controls: Optional[str] = None
|
|
124
|
+
class_: Optional[str] = None
|
|
125
|
+
cci_ref: list[str] = []
|
|
126
|
+
false_positives: Optional[str] = None
|
|
127
|
+
false_negatives: Optional[str] = None
|
|
128
|
+
documentable: Optional[str] = None
|
|
129
|
+
mitigations: Optional[str] = None
|
|
130
|
+
potential_impact: Optional[str] = None
|
|
131
|
+
third_party_tools: Optional[str] = None
|
|
132
|
+
mitigation_control: Optional[str] = None
|
|
133
|
+
responsibility: Optional[str] = None
|
|
134
|
+
security_override_guidance: Optional[str] = None
|
|
135
|
+
legacy_id: Optional[str] = None
|
|
136
|
+
status: Optional[str] = None
|
|
137
|
+
finding_details: Optional[str] = None
|
|
138
|
+
comments: Optional[str] = None
|
|
139
|
+
severity_override: Optional[str] = None
|
|
140
|
+
severity_justification: Optional[str] = None
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class STIG(BaseModel):
|
|
144
|
+
"""Data model for STIG including STIG information and vulnerabilities."""
|
|
145
|
+
|
|
146
|
+
baseline: str
|
|
147
|
+
stig_info: STIGInfo
|
|
148
|
+
vulns: List[Vuln] = []
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
def component_title(self) -> str:
|
|
152
|
+
"""
|
|
153
|
+
Extract the component title from the STIG title.
|
|
154
|
+
|
|
155
|
+
:return: The component title.
|
|
156
|
+
:rtype: str
|
|
157
|
+
"""
|
|
158
|
+
return stig_component_title(self.stig_info.title)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class Checklist(BaseModel):
|
|
162
|
+
"""Data model for Checklist including Asset and STIGs."""
|
|
163
|
+
|
|
164
|
+
assets: List[Asset] = []
|
|
165
|
+
stigs: List[STIG] = []
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def parse_element_to_dict(
|
|
169
|
+
element: Element,
|
|
170
|
+
key_tag: Optional[str] = None,
|
|
171
|
+
value_tag: Optional[str] = None,
|
|
172
|
+
list_fields: Optional[List[str]] = None,
|
|
173
|
+
) -> Dict[str, Any]:
|
|
174
|
+
"""
|
|
175
|
+
This function parses an XML element into a dictionary. It can operate in two modes:
|
|
176
|
+
1. If key_tag and value_tag are provided, it searches for these specific tags within each child element
|
|
177
|
+
to construct key-value pairs for the dictionary.
|
|
178
|
+
2. If key_tag and value_tag are not provided, it converts all child elements of the given element
|
|
179
|
+
directly to a dictionary with child tag names as keys and their text content as values.
|
|
180
|
+
|
|
181
|
+
:param Element element: The XML element to parse.
|
|
182
|
+
:param Optional[str] key_tag: Optional; the tag name that contains the key for key-value parsing mode.
|
|
183
|
+
:param Optional[str] value_tag: Optional; the tag name that contains the value for key-value parsing mode.
|
|
184
|
+
:param Optional[List[str]] list_fields: Optional; list of fields that are expected to have lists back.
|
|
185
|
+
:return: A dictionary representation of the element.
|
|
186
|
+
:rtype: Dict[str, Any]
|
|
187
|
+
"""
|
|
188
|
+
if list_fields is None:
|
|
189
|
+
list_fields = []
|
|
190
|
+
|
|
191
|
+
if key_tag and value_tag:
|
|
192
|
+
return _parse_key_value_pairs(element, key_tag, value_tag, list_fields)
|
|
193
|
+
else:
|
|
194
|
+
return _parse_direct_child_elements(element)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _parse_key_value_pairs(element: Element, key_tag: str, value_tag: str, list_fields: List[str]) -> Dict[str, Any]:
|
|
198
|
+
"""
|
|
199
|
+
Parse key-value pairs from XML element based on provided key and value tags.
|
|
200
|
+
|
|
201
|
+
:param Element element: The XML element to parse.
|
|
202
|
+
:param str key_tag: The tag name that contains the key for key-value parsing mode.
|
|
203
|
+
:param str value_tag: The tag name that contains the value for key-value parsing mode.
|
|
204
|
+
:param List[str] list_fields: List of fields that are expected to have lists back.
|
|
205
|
+
:return: A dictionary of parsed key-value pairs.
|
|
206
|
+
:rtype: Dict[str, Any]
|
|
207
|
+
"""
|
|
208
|
+
parsed_data: dict[str, Any] = {}
|
|
209
|
+
for child in element.findall(f".//{key_tag}"):
|
|
210
|
+
key = child.text
|
|
211
|
+
value_element = child.find(f"../{value_tag}")
|
|
212
|
+
value = getattr(value_element, "text", None)
|
|
213
|
+
if key:
|
|
214
|
+
key = snakify(key)
|
|
215
|
+
if key in list_fields:
|
|
216
|
+
parsed_data.setdefault(key, []).append(value) if value else None
|
|
217
|
+
else:
|
|
218
|
+
parsed_data[key] = value
|
|
219
|
+
return parsed_data
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _parse_direct_child_elements(element: Element) -> Dict[str, Any]:
|
|
223
|
+
"""
|
|
224
|
+
Parse direct child elements of an XML element into a dictionary.
|
|
225
|
+
|
|
226
|
+
:param Element element: The XML element to parse.
|
|
227
|
+
:return: A dictionary of direct child elements.
|
|
228
|
+
:rtype: Dict[str, Any]
|
|
229
|
+
"""
|
|
230
|
+
result = {}
|
|
231
|
+
for child in element:
|
|
232
|
+
key = snakify(child.tag)
|
|
233
|
+
# Use empty string for None to ensure the field is included
|
|
234
|
+
value = child.text if child.text is not None else ""
|
|
235
|
+
result[key] = value
|
|
236
|
+
return result
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def parse_checklist(file_path: Union[str, Path]) -> Checklist:
|
|
240
|
+
"""
|
|
241
|
+
Main function to parse a checklist from an XML file or S3 object using lxml for parsing.
|
|
242
|
+
|
|
243
|
+
:param Union[str, Path] file_path: The path to the XML file or S3 object.
|
|
244
|
+
:raises ValueError: If the ASSET element is not found in the XML
|
|
245
|
+
:return: Checklist object
|
|
246
|
+
:rtype: Checklist
|
|
247
|
+
"""
|
|
248
|
+
try:
|
|
249
|
+
import boto3
|
|
250
|
+
|
|
251
|
+
if isinstance(file_path, str) and file_path.startswith("s3://"):
|
|
252
|
+
# Handle S3 path
|
|
253
|
+
s3_parts = file_path[5:].split("/", 1)
|
|
254
|
+
bucket = s3_parts[0]
|
|
255
|
+
key = s3_parts[1]
|
|
256
|
+
|
|
257
|
+
s3 = boto3.client("s3")
|
|
258
|
+
response = s3.get_object(Bucket=bucket, Key=key)
|
|
259
|
+
content = response["Body"].read()
|
|
260
|
+
|
|
261
|
+
tree = et.parse(io.BytesIO(content))
|
|
262
|
+
root = tree.getroot()
|
|
263
|
+
file_name = key.split("/")[-1]
|
|
264
|
+
else:
|
|
265
|
+
# Handle local file path
|
|
266
|
+
xml_path = Path(file_path)
|
|
267
|
+
tree = et.parse(xml_path)
|
|
268
|
+
root = tree.getroot()
|
|
269
|
+
file_name = xml_path.name
|
|
270
|
+
|
|
271
|
+
# Parse the Assets
|
|
272
|
+
asset_elem = root.find("ASSET")
|
|
273
|
+
if asset_elem is None:
|
|
274
|
+
raise ValueError("ASSET element not found in XML.")
|
|
275
|
+
assets = [Asset(**parse_element_to_dict(asset_elem))]
|
|
276
|
+
|
|
277
|
+
stigs = [
|
|
278
|
+
STIG(
|
|
279
|
+
baseline=file_name.split(".")[0].split("-")[0].strip(),
|
|
280
|
+
stig_info=STIGInfo(**parse_element_to_dict(istig_elem, key_tag="SID_NAME", value_tag="SID_DATA")),
|
|
281
|
+
vulns=[
|
|
282
|
+
Vuln(
|
|
283
|
+
**parse_element_to_dict(
|
|
284
|
+
vuln_elem,
|
|
285
|
+
key_tag="VULN_ATTRIBUTE",
|
|
286
|
+
value_tag="ATTRIBUTE_DATA",
|
|
287
|
+
list_fields=[
|
|
288
|
+
"cci_ref",
|
|
289
|
+
],
|
|
290
|
+
),
|
|
291
|
+
status=vuln_elem.findtext("STATUS"),
|
|
292
|
+
finding_details=vuln_elem.findtext("FINDING_DETAILS"),
|
|
293
|
+
comments=vuln_elem.findtext("COMMENTS"),
|
|
294
|
+
severity_override=vuln_elem.findtext("SEVERITY_OVERRIDE"),
|
|
295
|
+
severity_justification=vuln_elem.findtext("SEVERITY_JUSTIFICATION"),
|
|
296
|
+
)
|
|
297
|
+
for vuln_elem in istig_elem.findall("VULN")
|
|
298
|
+
],
|
|
299
|
+
)
|
|
300
|
+
for istig_elem in root.findall(".//iSTIG")
|
|
301
|
+
if istig_elem.find("STIG_INFO") is not None
|
|
302
|
+
]
|
|
303
|
+
|
|
304
|
+
return Checklist(assets=assets, stigs=stigs)
|
|
305
|
+
except Exception as e:
|
|
306
|
+
logger.error(f"Error parsing {file_path}: {e}")
|
|
307
|
+
raise e
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def get_components_from_checklist(
|
|
311
|
+
checklist: Checklist,
|
|
312
|
+
) -> Generator[dict[str, str], None, None]:
|
|
313
|
+
"""
|
|
314
|
+
Extract the component titles from the checklist using a generator expression.
|
|
315
|
+
|
|
316
|
+
:param Checklist checklist: The checklist object.
|
|
317
|
+
:return: A generator of component titles.
|
|
318
|
+
:rtype: Generator[dict[str, str], None, None]
|
|
319
|
+
"""
|
|
320
|
+
return ({stig.stig_info.stigid: stig_component_title(stig.stig_info.title)} for stig in checklist.stigs)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def get_all_components_from_checklists(checklists: List[Checklist]) -> dict[str, str]:
|
|
324
|
+
"""
|
|
325
|
+
Extract the component titles from a list of checklists using a generator expression.
|
|
326
|
+
|
|
327
|
+
:param List[Checklist] checklists: The list of checklist objects.
|
|
328
|
+
:return: A dictionary of unique component titles.
|
|
329
|
+
:rtype: dict[str, str]
|
|
330
|
+
"""
|
|
331
|
+
components: dict[str, str] = {}
|
|
332
|
+
for checklist in checklists:
|
|
333
|
+
# Collect all components from the generator into a single dictionary
|
|
334
|
+
components.update({k: v for d in get_components_from_checklist(checklist) for k, v in d.items()})
|
|
335
|
+
return components
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def get_all_assets_from_checklists(checklists: List[Checklist]) -> List[Asset]:
|
|
339
|
+
"""
|
|
340
|
+
Extract the assets from a list of checklists.
|
|
341
|
+
|
|
342
|
+
:param List[Checklist] checklists: The list of checklist objects.
|
|
343
|
+
:return: A list of unique assets.
|
|
344
|
+
:rtype: List[Asset]
|
|
345
|
+
"""
|
|
346
|
+
assets = set()
|
|
347
|
+
for checklist in checklists:
|
|
348
|
+
assets.update(checklist.assets)
|
|
349
|
+
return list(assets)
|