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,1946 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Integrates OSCAL into RegScale"""
|
|
4
|
+
|
|
5
|
+
# standard python imports
|
|
6
|
+
import json
|
|
7
|
+
import re
|
|
8
|
+
import tempfile
|
|
9
|
+
import uuid
|
|
10
|
+
from os import remove, sep
|
|
11
|
+
from typing import Tuple, Union, Optional
|
|
12
|
+
|
|
13
|
+
import click
|
|
14
|
+
import requests
|
|
15
|
+
import xmltodict
|
|
16
|
+
import yaml
|
|
17
|
+
from bs4 import BeautifulSoup
|
|
18
|
+
from packaging import version as package_version
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
from regscale.core.app.api import Api
|
|
22
|
+
from regscale.core.app.application import Application
|
|
23
|
+
from regscale.core.app.logz import create_logger
|
|
24
|
+
from regscale.core.app.utils.app_utils import (
|
|
25
|
+
check_file_path,
|
|
26
|
+
create_progress_object,
|
|
27
|
+
error_and_exit,
|
|
28
|
+
find_keys,
|
|
29
|
+
get_file_name,
|
|
30
|
+
reformat_str_date,
|
|
31
|
+
save_data_to,
|
|
32
|
+
)
|
|
33
|
+
from regscale.models.regscale_models import Catalog, ControlImplementation, Component, SecurityControl
|
|
34
|
+
from regscale.utils.threading.threadhandler import create_threads, thread_assignment
|
|
35
|
+
|
|
36
|
+
# create global variables
|
|
37
|
+
job_progress = create_progress_object()
|
|
38
|
+
logger = create_logger()
|
|
39
|
+
OBJ_TO_CONTROLS = False
|
|
40
|
+
|
|
41
|
+
(
|
|
42
|
+
new_controls,
|
|
43
|
+
new_regscale_controls,
|
|
44
|
+
errors,
|
|
45
|
+
new_params,
|
|
46
|
+
new_tests,
|
|
47
|
+
new_objectives,
|
|
48
|
+
updates,
|
|
49
|
+
) = ([], [], [], [], [], [], [])
|
|
50
|
+
SC_URL = "/api/securitycontrols/"
|
|
51
|
+
UL_CLOSE = "</ul>"
|
|
52
|
+
LI = "<li>"
|
|
53
|
+
LI_CLOSE = "</li>"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@click.group()
|
|
57
|
+
def oscal():
|
|
58
|
+
"""Performs bulk processing of OSCAL files."""
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# OSCAL Version Support
|
|
62
|
+
@oscal.command()
|
|
63
|
+
def version():
|
|
64
|
+
"""Info on current OSCAL version supported by RegScale."""
|
|
65
|
+
logger.info("RegScale currently supports OSCAL Version 1.0.")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def convert_oscal_comp_to_regscale(j_data: dict) -> None:
|
|
69
|
+
"""
|
|
70
|
+
Convert OSCAL component dict into a RegScale Component
|
|
71
|
+
|
|
72
|
+
:param dict j_data: OSCAL component
|
|
73
|
+
:rtype: None
|
|
74
|
+
"""
|
|
75
|
+
app = Application()
|
|
76
|
+
config = app.config
|
|
77
|
+
api = Api()
|
|
78
|
+
component_id = None
|
|
79
|
+
regscale_components = []
|
|
80
|
+
components = []
|
|
81
|
+
existing_components = api.get(url=config["domain"] + "/api/components/getList").json()
|
|
82
|
+
controls_to_be_added = []
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
components = j_data["component-definition"]["components"]
|
|
86
|
+
except KeyError as kex:
|
|
87
|
+
error_and_exit(f"Key Error! {kex}")
|
|
88
|
+
|
|
89
|
+
for comp in components:
|
|
90
|
+
control_implementations = comp["control-implementations"]
|
|
91
|
+
|
|
92
|
+
base_catalog = ""
|
|
93
|
+
title = ""
|
|
94
|
+
|
|
95
|
+
# alternative
|
|
96
|
+
if len(comp["control-implementations"]) > 0:
|
|
97
|
+
base_catalog = comp["control-implementations"][0]["source"]
|
|
98
|
+
else:
|
|
99
|
+
logger.info(f"Component {comp['title']} contains no control implementation statements")
|
|
100
|
+
|
|
101
|
+
component = Component(
|
|
102
|
+
title=comp.get("title", ""),
|
|
103
|
+
componentOwnerId=app.config["userId"],
|
|
104
|
+
componentType=comp.get("type", "").lower(),
|
|
105
|
+
description=comp.get("description", ""),
|
|
106
|
+
purpose=comp.get("purpose", ""),
|
|
107
|
+
)
|
|
108
|
+
regscale_components.append(component.dict())
|
|
109
|
+
for control_implements in control_implementations:
|
|
110
|
+
if "implemented-requirements" in control_implements:
|
|
111
|
+
for control_data in control_implements["implemented-requirements"]:
|
|
112
|
+
control_data = {
|
|
113
|
+
"component": comp["title"],
|
|
114
|
+
# "id": control_data["control-id"],
|
|
115
|
+
"title": control_data["control-id"],
|
|
116
|
+
"description": control_data["description"],
|
|
117
|
+
}
|
|
118
|
+
controls_to_be_added.append(control_data)
|
|
119
|
+
elif base_catalog and "implemented-requirements" not in control_implements:
|
|
120
|
+
# Get control data from base catalog
|
|
121
|
+
logger.debug("base_catalog: %s\ntitle: %s", base_catalog, title)
|
|
122
|
+
|
|
123
|
+
for reg in regscale_components:
|
|
124
|
+
check_component = [x for x in existing_components if x["title"] == reg["title"]]
|
|
125
|
+
if not check_component:
|
|
126
|
+
response = api.post(url=f'{config["domain"]}/api/components/', json=reg)
|
|
127
|
+
if not response.raise_for_status():
|
|
128
|
+
component_id = response.json()["id"]
|
|
129
|
+
logger.info("Successfully posted %s to RegScale.", reg["title"])
|
|
130
|
+
else:
|
|
131
|
+
for cmp in check_component:
|
|
132
|
+
# update the id for the reg object
|
|
133
|
+
reg["id"] = cmp["id"]
|
|
134
|
+
response = api.put(url=f'{config["domain"]}/api/components/{cmp["id"]}', json=reg)
|
|
135
|
+
if not response.raise_for_status():
|
|
136
|
+
component_id = cmp["id"]
|
|
137
|
+
logger.info("Successfully updated component %s in RegScale.", cmp["title"])
|
|
138
|
+
# Load controls to RegScale and associate with new component
|
|
139
|
+
load_controls(
|
|
140
|
+
controls_to_be_added=controls_to_be_added,
|
|
141
|
+
component_id=component_id,
|
|
142
|
+
base_catalog=base_catalog,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@oscal.command(name="component")
|
|
147
|
+
@click.option(
|
|
148
|
+
"--file_name",
|
|
149
|
+
type=click.Path(exists=True, dir_okay=False),
|
|
150
|
+
help="Enter the path to the file to parse OSCAL Complonents.",
|
|
151
|
+
required=True,
|
|
152
|
+
)
|
|
153
|
+
def upload_component(file_name: str) -> None:
|
|
154
|
+
"""Upload OSCAL Component to RegScale."""
|
|
155
|
+
# Load Controls and assign to component
|
|
156
|
+
process_component(file_name=file_name)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def load_controls(controls_to_be_added: list[dict], component_id: int, base_catalog: str) -> None:
|
|
160
|
+
"""
|
|
161
|
+
Load control implementations to RegScale
|
|
162
|
+
|
|
163
|
+
:param list[dict] controls_to_be_added: Controls to load from RegScale
|
|
164
|
+
:param int component_id: Component ID from RegScale
|
|
165
|
+
:param str base_catalog: base catalogue to filter components
|
|
166
|
+
:rtype: None
|
|
167
|
+
"""
|
|
168
|
+
app = Application()
|
|
169
|
+
config = app.config
|
|
170
|
+
all_implementations = [
|
|
171
|
+
imp.dict()
|
|
172
|
+
for imp in ControlImplementation.get_all_by_parent(parent_id=component_id, parent_module="components")
|
|
173
|
+
]
|
|
174
|
+
|
|
175
|
+
if cat := [cat.dict() for cat in Catalog.get_list() if cat.title == base_catalog]:
|
|
176
|
+
cat_id = cat[0]["id"]
|
|
177
|
+
controls = [control.dict() for control in SecurityControl.get_list_by_catalog(catalog_id=cat_id)]
|
|
178
|
+
for imp_control in controls_to_be_added:
|
|
179
|
+
logger.debug("imp_control: %s", imp_control)
|
|
180
|
+
try:
|
|
181
|
+
if reg_control := [
|
|
182
|
+
control for control in controls if control["controlId"].lower() == imp_control["title"].lower()
|
|
183
|
+
]:
|
|
184
|
+
control_implementation = ControlImplementation(
|
|
185
|
+
parentId=component_id,
|
|
186
|
+
parentModule="components",
|
|
187
|
+
controlOwnerId=config["userId"],
|
|
188
|
+
status="Fully Implemented",
|
|
189
|
+
controlID=reg_control[0]["id"],
|
|
190
|
+
implementation=imp_control["description"],
|
|
191
|
+
)
|
|
192
|
+
control = SecurityControl.lookup_control(app=app, control_id=control_implementation.controlID)
|
|
193
|
+
create_or_update_control(
|
|
194
|
+
control=control,
|
|
195
|
+
control_implementation=control_implementation,
|
|
196
|
+
all_implementations=all_implementations,
|
|
197
|
+
)
|
|
198
|
+
except requests.RequestException as rex:
|
|
199
|
+
logger.error(rex)
|
|
200
|
+
else:
|
|
201
|
+
logger.debug(f"Catalog '{base_catalog}' not found")
|
|
202
|
+
|
|
203
|
+
# Insert or update new control implementations
|
|
204
|
+
ControlImplementation.bulk_save()
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def create_or_update_control(
|
|
208
|
+
control: SecurityControl, control_implementation: ControlImplementation, all_implementations: list[dict]
|
|
209
|
+
) -> None:
|
|
210
|
+
"""
|
|
211
|
+
Create or update control implementation
|
|
212
|
+
|
|
213
|
+
:param SecurityControl control: Control object
|
|
214
|
+
:param ControlImplementation control_implementation: Control implementation object
|
|
215
|
+
:param list[dict] all_implementations: List of all control implementations
|
|
216
|
+
:rtype: None
|
|
217
|
+
"""
|
|
218
|
+
try:
|
|
219
|
+
if control.controlId not in [
|
|
220
|
+
imp["controlName"] for imp in all_implementations if imp["parentId"] == control_implementation.parentId
|
|
221
|
+
]:
|
|
222
|
+
control_implementation.create(bulk=True)
|
|
223
|
+
else:
|
|
224
|
+
dat = [
|
|
225
|
+
imp
|
|
226
|
+
for imp in all_implementations
|
|
227
|
+
if imp["controlName"] == control.controlId and imp["parentId"] == control_implementation.parentId
|
|
228
|
+
][0]
|
|
229
|
+
dat["implementation"] = control_implementation.implementation
|
|
230
|
+
dat["status"] = control_implementation.status
|
|
231
|
+
ControlImplementation(**dat).save(bulk=True)
|
|
232
|
+
except IndexError as iex:
|
|
233
|
+
logger.error("Index Error: %s\n%s", dat, iex)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def process_component(file_name: str) -> None:
|
|
237
|
+
"""
|
|
238
|
+
OSCAL Component to RegScale
|
|
239
|
+
|
|
240
|
+
:param str file_name: File Name
|
|
241
|
+
:rtype: None
|
|
242
|
+
"""
|
|
243
|
+
output_name = tempfile.gettempdir() + sep + "component.json"
|
|
244
|
+
logger.debug(file_name)
|
|
245
|
+
file_convert_json(file_name, output_name)
|
|
246
|
+
try:
|
|
247
|
+
json_d = open(output_name, "r").read()
|
|
248
|
+
except FileNotFoundError:
|
|
249
|
+
error_and_exit(f"File not found!\n{file_name}")
|
|
250
|
+
convert_oscal_comp_to_regscale(j_data=json.loads(json_d))
|
|
251
|
+
remove(output_name)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def file_convert_json(input: str, output: str) -> None:
|
|
255
|
+
"""
|
|
256
|
+
Convert file from YML/XML to JSON
|
|
257
|
+
|
|
258
|
+
:param str input: Path to the original file to convert
|
|
259
|
+
:param str output: Desired path of the converted file
|
|
260
|
+
:rtype: None
|
|
261
|
+
"""
|
|
262
|
+
# Create object
|
|
263
|
+
with open(input, "r") as file_in, open(output, "w") as file_out:
|
|
264
|
+
if Path(input).suffix == ".json":
|
|
265
|
+
obj = json.load(file_in)
|
|
266
|
+
if Path(input).suffix == ".xml":
|
|
267
|
+
obj = xmltodict.parse((file_in.read()))
|
|
268
|
+
if Path(input).suffix in [".yaml", ".yml"]:
|
|
269
|
+
obj = yaml.safe_load(file_in.read())
|
|
270
|
+
json.dump(obj, file_out)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
# OSCAL Profile Loader Support
|
|
274
|
+
@oscal.command()
|
|
275
|
+
@click.option(
|
|
276
|
+
"--title",
|
|
277
|
+
type=click.STRING,
|
|
278
|
+
help="RegScale will name the profile with the title provided.",
|
|
279
|
+
prompt="Enter the title for the OSCAL profile",
|
|
280
|
+
required=True,
|
|
281
|
+
)
|
|
282
|
+
@click.option(
|
|
283
|
+
"--categorization",
|
|
284
|
+
type=click.Choice(["Low", "Moderate", "High"], case_sensitive=False),
|
|
285
|
+
help="Choose from Low, Moderate, or High.",
|
|
286
|
+
prompt="Enter the FIPS categorization level",
|
|
287
|
+
required=True,
|
|
288
|
+
)
|
|
289
|
+
@click.option(
|
|
290
|
+
"--catalog",
|
|
291
|
+
type=click.INT,
|
|
292
|
+
help="Primary key (unique ID) of the RegScale catalogue.",
|
|
293
|
+
prompt="Enter the RegScale Catalogue ID to use",
|
|
294
|
+
required=True,
|
|
295
|
+
)
|
|
296
|
+
@click.option(
|
|
297
|
+
"--file_name",
|
|
298
|
+
type=click.Path(exists=True, dir_okay=False, file_okay=True),
|
|
299
|
+
help="RegScale will process and load the profile along with all specified controls.",
|
|
300
|
+
prompt="Enter the file name of the OSCAL profile to process",
|
|
301
|
+
required=True,
|
|
302
|
+
)
|
|
303
|
+
def profile(title: str, categorization: str, catalog: int, file_name: Path):
|
|
304
|
+
"""OSCAL Profile Loader."""
|
|
305
|
+
upload_profile(title=title, categorization=categorization, catalog=catalog, file_name=file_name)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
# flake8: noqa: C901
|
|
309
|
+
def upload_profile(title: str, categorization: str, catalog: int, file_name: Path) -> None:
|
|
310
|
+
"""
|
|
311
|
+
OSCAL Profile Uploader
|
|
312
|
+
|
|
313
|
+
:param str title: Title
|
|
314
|
+
:param str categorization: Category information
|
|
315
|
+
:param int catalog: Catalogue Title
|
|
316
|
+
:param Path file_name: Desired file name
|
|
317
|
+
:rtype: None
|
|
318
|
+
"""
|
|
319
|
+
app = Application()
|
|
320
|
+
config = app.config
|
|
321
|
+
api = Api()
|
|
322
|
+
# validation
|
|
323
|
+
if catalog <= 0:
|
|
324
|
+
error_and_exit("No catalogue provided or catalogue invalid.")
|
|
325
|
+
elif categorization.title() not in ["Low", "Moderate", "High"]:
|
|
326
|
+
error_and_exit("Categorization not provided or invalid.")
|
|
327
|
+
|
|
328
|
+
# load the catalog
|
|
329
|
+
try:
|
|
330
|
+
oscal = open(file_name, "r", encoding="utf-8-sig")
|
|
331
|
+
oscal_data = json.load(oscal)
|
|
332
|
+
except Exception as ex:
|
|
333
|
+
logger.debug(file_name)
|
|
334
|
+
error_and_exit(f"Unable to open the specified OSCAL file for processing.\n{ex}")
|
|
335
|
+
# load the config from YAML
|
|
336
|
+
try:
|
|
337
|
+
config = app.load_config()
|
|
338
|
+
except FileNotFoundError:
|
|
339
|
+
error_and_exit("Unable to open the init.yaml file.")
|
|
340
|
+
|
|
341
|
+
global schema
|
|
342
|
+
|
|
343
|
+
# set headers
|
|
344
|
+
str_user = config["userId"]
|
|
345
|
+
headers = {"Accept": "application/json", "Authorization": config["token"]}
|
|
346
|
+
|
|
347
|
+
# create a new profile
|
|
348
|
+
profile = {
|
|
349
|
+
"id": 0,
|
|
350
|
+
"uuid": "",
|
|
351
|
+
"name": title,
|
|
352
|
+
"confidentiality": "",
|
|
353
|
+
"integrity": "",
|
|
354
|
+
"availability": "",
|
|
355
|
+
"category": categorization,
|
|
356
|
+
"profileOwnerId": str_user,
|
|
357
|
+
"createdById": str_user,
|
|
358
|
+
"dateCreated": None,
|
|
359
|
+
"lastUpdatedById": str_user,
|
|
360
|
+
"dateLastUpdated": None,
|
|
361
|
+
"isPublic": True,
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
# create the profile
|
|
365
|
+
url_prof = f'{config["domain"]}/api/profiles/'
|
|
366
|
+
logger.info("RegScale creating a new profile...")
|
|
367
|
+
try:
|
|
368
|
+
prof_response = api.post(url=url_prof, headers=headers, json=profile)
|
|
369
|
+
prof_json_response = prof_response.json()
|
|
370
|
+
logger.info("\nProfile ID: " + str(prof_json_response["id"]))
|
|
371
|
+
# get the profile ID
|
|
372
|
+
int_profile = prof_json_response["id"]
|
|
373
|
+
except requests.exceptions.RequestException as ex:
|
|
374
|
+
error_and_exit(f"Unable to create profile in RegScale.\n{ex}")
|
|
375
|
+
|
|
376
|
+
# get the list of existing controls for the catalog
|
|
377
|
+
url_sc = f'{config["domain"]}/api/SecurityControls/getList/{catalog}'
|
|
378
|
+
try:
|
|
379
|
+
sc_response = api.get(url_sc, headers=headers)
|
|
380
|
+
sc_data = sc_response.json()
|
|
381
|
+
except requests.exceptions.RequestException as ex:
|
|
382
|
+
error_and_exit(
|
|
383
|
+
f"Unable to retrieve security controls for this catalogue in RegScale.\nError: \
|
|
384
|
+
{ex}\n{sc_response.text}"
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
# loop through each item in the OSCAL control set
|
|
388
|
+
mappings = []
|
|
389
|
+
for m in oscal_data["profile"]["imports"][0]["include-controls"][0]["with-ids"]:
|
|
390
|
+
b_match = False
|
|
391
|
+
for sc in sc_data:
|
|
392
|
+
if m == sc["controlId"]:
|
|
393
|
+
b_match = True
|
|
394
|
+
mapping = {
|
|
395
|
+
"id": 0,
|
|
396
|
+
"profileID": int_profile,
|
|
397
|
+
"controlID": int(sc["id"]),
|
|
398
|
+
}
|
|
399
|
+
mappings.append(mapping)
|
|
400
|
+
break
|
|
401
|
+
if b_match is False:
|
|
402
|
+
logger.error("Unable to locate control: %s.", m)
|
|
403
|
+
|
|
404
|
+
# upload the controls to the profile as mappings
|
|
405
|
+
url_maps = config["domain"] + "/api/profileMapping/batchCreate"
|
|
406
|
+
try:
|
|
407
|
+
api.post(url_maps, headers=headers, json=mappings)
|
|
408
|
+
logger.info(
|
|
409
|
+
"%s total mappings created in RegScale for this profile.",
|
|
410
|
+
str(len(mappings)),
|
|
411
|
+
)
|
|
412
|
+
except requests.exceptions.RequestException as ex:
|
|
413
|
+
error_and_exit(f"Unable to create mappings for this profile in RegScale.\n{ex}")
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
# Process catalog from OSCAL
|
|
417
|
+
@oscal.command()
|
|
418
|
+
@click.option(
|
|
419
|
+
"--file_name",
|
|
420
|
+
type=click.Path(exists=True, dir_okay=False, file_okay=True),
|
|
421
|
+
help="RegScale will process and load the catalogue along with all controls, statements, and \
|
|
422
|
+
parameters.",
|
|
423
|
+
prompt="Enter the file name of catalogue in NIST OSCAL to process",
|
|
424
|
+
required=True,
|
|
425
|
+
)
|
|
426
|
+
@click.option(
|
|
427
|
+
"--obj_to_controls",
|
|
428
|
+
type=click.BOOL,
|
|
429
|
+
default=0,
|
|
430
|
+
show_default=True,
|
|
431
|
+
help="Convert objectives to RegScale controls.",
|
|
432
|
+
)
|
|
433
|
+
@click.option(
|
|
434
|
+
"--fedramp",
|
|
435
|
+
type=click.BOOL,
|
|
436
|
+
default=0,
|
|
437
|
+
show_default=True,
|
|
438
|
+
required=False,
|
|
439
|
+
help="Specific processing for using the FedRAMP namespace to handle response points and assessment objectives differently",
|
|
440
|
+
)
|
|
441
|
+
@click.option(
|
|
442
|
+
"--new_catalog_name",
|
|
443
|
+
type=click.STRING,
|
|
444
|
+
help="RegScale will give the catalogue this new name.",
|
|
445
|
+
required=False,
|
|
446
|
+
default=None,
|
|
447
|
+
)
|
|
448
|
+
def catalog(
|
|
449
|
+
file_name: Path,
|
|
450
|
+
obj_to_controls: click.BOOL,
|
|
451
|
+
fedramp: click.BOOL,
|
|
452
|
+
new_catalog_name: str,
|
|
453
|
+
):
|
|
454
|
+
"""Process and load catalog to RegScale."""
|
|
455
|
+
upload_catalog(
|
|
456
|
+
file_name=file_name,
|
|
457
|
+
obj_to_controls=obj_to_controls,
|
|
458
|
+
fedramp=fedramp,
|
|
459
|
+
new_catalog_name=new_catalog_name,
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
# flake8: noqa: C901
|
|
464
|
+
def upload_catalog(
|
|
465
|
+
file_name: Path,
|
|
466
|
+
obj_to_controls: click.BOOL = 0,
|
|
467
|
+
fedramp: click.BOOL = 0,
|
|
468
|
+
new_catalog_name: str = None,
|
|
469
|
+
) -> None:
|
|
470
|
+
"""
|
|
471
|
+
Process and load catalogue to RegScale
|
|
472
|
+
|
|
473
|
+
:param Path file_name: Path to the catalogue to upload
|
|
474
|
+
:param click.BOOL obj_to_controls: Flag to indicate converting objectives to controls
|
|
475
|
+
:param click.BOOL fedramp: Flag to indicate it is a FedRAMP catalog so it processes differently
|
|
476
|
+
:param str new_catalog_name: New name to give the catalogue when uploaded, defaults to None
|
|
477
|
+
:rtype: None
|
|
478
|
+
"""
|
|
479
|
+
import pandas as pd # Optimize import performance
|
|
480
|
+
|
|
481
|
+
app = Application()
|
|
482
|
+
config = app.config
|
|
483
|
+
api = Api()
|
|
484
|
+
# Create directory if not exists
|
|
485
|
+
check_file_path("processing")
|
|
486
|
+
|
|
487
|
+
# load the catalog
|
|
488
|
+
try:
|
|
489
|
+
with open(file_name, "r", encoding="utf-8-sig") as input:
|
|
490
|
+
oscal_data = json.load(input)
|
|
491
|
+
except requests.exceptions.RequestException as ex:
|
|
492
|
+
error_and_exit(f"Unable to open the specified OSCAL file for processing.\n{ex}")
|
|
493
|
+
# load the config from YAML
|
|
494
|
+
try:
|
|
495
|
+
config = app.load_config()
|
|
496
|
+
except Exception:
|
|
497
|
+
error_and_exit("Unable to open the init file.")
|
|
498
|
+
# debug flag to pause upload when testing and debugging (always true for production CLI use)
|
|
499
|
+
upload_flag = params_flag = tests_flag = objects_flag = deep_links_flag = True
|
|
500
|
+
|
|
501
|
+
# set headers
|
|
502
|
+
str_user = config["userId"]
|
|
503
|
+
|
|
504
|
+
# parse the OSCAL JSON to get related data (used to enrich base spreadsheet)
|
|
505
|
+
catalog_arr = (
|
|
506
|
+
oscal_data["catalog"]
|
|
507
|
+
if "catalog" in oscal_data
|
|
508
|
+
else error_and_exit("catalogue key not found in dataset, exiting..")
|
|
509
|
+
)
|
|
510
|
+
str_uuid = catalog_arr["uuid"]
|
|
511
|
+
metadata = catalog_arr["metadata"]
|
|
512
|
+
global schema
|
|
513
|
+
schema = oscal_version(metadata)
|
|
514
|
+
resource_guid = ""
|
|
515
|
+
resource_title = ""
|
|
516
|
+
citation = ""
|
|
517
|
+
links = ""
|
|
518
|
+
|
|
519
|
+
# process resources for lookup
|
|
520
|
+
resources = []
|
|
521
|
+
if "back-matter" in catalog_arr:
|
|
522
|
+
back_matter_arr = catalog_arr["back-matter"]
|
|
523
|
+
for i in back_matter_arr["resources"]:
|
|
524
|
+
# make sure values exist
|
|
525
|
+
if "title" in i:
|
|
526
|
+
resource_title = i["title"]
|
|
527
|
+
if "uuid" in i:
|
|
528
|
+
resource_guid = i["uuid"]
|
|
529
|
+
if "citation" in i:
|
|
530
|
+
citation = i["citation"]
|
|
531
|
+
if "text" in citation:
|
|
532
|
+
citation = citation["text"]
|
|
533
|
+
links = ""
|
|
534
|
+
if "rlinks" in i:
|
|
535
|
+
links = i["rlinks"]
|
|
536
|
+
if len(links) > 1:
|
|
537
|
+
for x in links:
|
|
538
|
+
if "href" in x:
|
|
539
|
+
links += x["href"] + "<br/>"
|
|
540
|
+
elif isinstance(links[0], dict) and len(links) == 1:
|
|
541
|
+
links = links[0].get("href")
|
|
542
|
+
# add parsed/flattened resource to the array
|
|
543
|
+
res = {
|
|
544
|
+
"uuid": resource_guid,
|
|
545
|
+
"short": resource_title,
|
|
546
|
+
"title": citation,
|
|
547
|
+
"links": links,
|
|
548
|
+
}
|
|
549
|
+
resources.append(res)
|
|
550
|
+
|
|
551
|
+
# Write to file to visualize the output
|
|
552
|
+
save_data_to(
|
|
553
|
+
file=Path("./processing/resources.json"),
|
|
554
|
+
data=resources,
|
|
555
|
+
output_log=False,
|
|
556
|
+
)
|
|
557
|
+
# convert data to pandas dataframe
|
|
558
|
+
raw_data = pd.DataFrame(resources)
|
|
559
|
+
|
|
560
|
+
# copy the columns of data that we want while renaming them to a specific case
|
|
561
|
+
resource_data = (
|
|
562
|
+
raw_data[["uuid", "title", "links"]].copy().rename(columns={"uuid": "UUID", "title": "Title", "links": "Links"})
|
|
563
|
+
)
|
|
564
|
+
|
|
565
|
+
# convert the data table to an HTML formatted table
|
|
566
|
+
str_resources = resource_data.to_html(index=False, justify="left")
|
|
567
|
+
|
|
568
|
+
# determine the date to use
|
|
569
|
+
date_format = "%Y-%m-%d %H:%M:%S.%f %z"
|
|
570
|
+
if "fedramp" in catalog_arr["metadata"]["version"].lower():
|
|
571
|
+
# format the dates into strings for RegScale
|
|
572
|
+
date_published = reformat_str_date(catalog_arr["metadata"]["published"], dt_format=date_format)
|
|
573
|
+
last_modified = reformat_str_date(
|
|
574
|
+
catalog_arr["metadata"]["last-modified"],
|
|
575
|
+
dt_format=date_format,
|
|
576
|
+
)
|
|
577
|
+
else:
|
|
578
|
+
# published date is required in RegScale but not found in NIST OSCAL catalog. Defaulting to last-modified date which is required in NIST OSCAL catalog
|
|
579
|
+
date_published = reformat_str_date(
|
|
580
|
+
catalog_arr["metadata"]["last-modified"],
|
|
581
|
+
dt_format=date_format,
|
|
582
|
+
)
|
|
583
|
+
last_modified = reformat_str_date(
|
|
584
|
+
catalog_arr["metadata"]["last-modified"],
|
|
585
|
+
dt_format=date_format,
|
|
586
|
+
)
|
|
587
|
+
|
|
588
|
+
# setup catalog data
|
|
589
|
+
cat = Catalog(
|
|
590
|
+
**{
|
|
591
|
+
"title": (
|
|
592
|
+
catalog_arr["metadata"]["title"]
|
|
593
|
+
if (new_catalog_name is None or new_catalog_name == "")
|
|
594
|
+
else new_catalog_name
|
|
595
|
+
),
|
|
596
|
+
"description": "This publication provides a catalog of security and privacy controls for information systems and organizations to protect organizational operations and assets, individuals, other organizations, and the Nation from a diverse set of threats and risks, including hostile attacks, human errors, natural disasters, structural failures, foreign intelligence entities, and privacy risks. <br/><br/><strong>Resources</strong><br/><br/>"
|
|
597
|
+
+ str_resources,
|
|
598
|
+
"datePublished": date_published,
|
|
599
|
+
"uuid": str_uuid,
|
|
600
|
+
"lastRevisionDate": last_modified,
|
|
601
|
+
"url": "https://csrc.nist.gov/",
|
|
602
|
+
"abstract": "This publication provides a catalog of security and privacy controls for federal information systems and organizations and a process for selecting controls to protect organizational operations (including mission, functions, image, and reputation), organizational assets, individuals, other organizations, and the Nation from a diverse set of threats including hostile cyber attacks, natural disasters, structural failures, and human errors (both intentional and unintentional). The security and privacy controls are customizable and implemented as part of an organization-wide process that manages information security and privacy risk. The controls address a diverse set of security and privacy requirements across the federal government and critical infrastructure, derived from legislation, Executive Orders, policies, directives, regulations, standards, and/or mission/business needs. The publication also describes how to develop specialized sets of controls, or overlays, tailored for specific types of missions/business functions, technologies, or environments of operation. Finally, the catalog of security controls addresses security from both a functionality perspective (the strength of security functions and mechanisms provided) and an assurance perspective (the measures of confidence in the implemented security capability). Addressing both security functionality and assurance helps to ensure that information technology component products and the information systems built from those products using sound system and security engineering principles are sufficiently trustworthy.",
|
|
603
|
+
"keywords": "FIPS Publication 200; FISMA; Privacy Act; Risk Management Framework; security controls; FIPS Publication 199; security requirements; computer security; assurance;",
|
|
604
|
+
"createdById": str_user,
|
|
605
|
+
"lastUpdatedById": str_user,
|
|
606
|
+
}
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
# create the catalog and print success result
|
|
610
|
+
if upload_flag is True:
|
|
611
|
+
logger.debug("Creating new catalogue in RegScale.")
|
|
612
|
+
# update the timeout
|
|
613
|
+
if api.timeout < 120:
|
|
614
|
+
api.timeout = 120
|
|
615
|
+
new_cat = cat.create()
|
|
616
|
+
catalogue_id = new_cat.id
|
|
617
|
+
logger.info("Created Catalogue ID: %s in RegScale", catalogue_id)
|
|
618
|
+
# get the catalog ID
|
|
619
|
+
else:
|
|
620
|
+
# don't set ID in debug mode
|
|
621
|
+
catalogue_id = 0
|
|
622
|
+
|
|
623
|
+
# process NIST families of controls
|
|
624
|
+
families = []
|
|
625
|
+
oscal_controls = []
|
|
626
|
+
parameters = []
|
|
627
|
+
parts = []
|
|
628
|
+
assessments = []
|
|
629
|
+
objectives = []
|
|
630
|
+
|
|
631
|
+
# process groups of controls
|
|
632
|
+
groups = catalog_arr["groups"]
|
|
633
|
+
|
|
634
|
+
for i in groups:
|
|
635
|
+
str_family = i["title"]
|
|
636
|
+
f = {
|
|
637
|
+
"id": (i["id"] if package_version.parse(schema) <= package_version.parse("1.0.2") else None),
|
|
638
|
+
"title": i["title"],
|
|
639
|
+
}
|
|
640
|
+
# add parsed item to the family array
|
|
641
|
+
families.append(f)
|
|
642
|
+
|
|
643
|
+
controls = (
|
|
644
|
+
list(find_keys(i, "controls"))
|
|
645
|
+
if package_version.parse(schema) > package_version.parse("1.0.2")
|
|
646
|
+
else i["controls"]
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
# loop through controls
|
|
650
|
+
for ctrl in controls:
|
|
651
|
+
# process the control
|
|
652
|
+
if isinstance(ctrl, dict):
|
|
653
|
+
oscal_controls = append_controls(
|
|
654
|
+
oscal_controls,
|
|
655
|
+
ctrl,
|
|
656
|
+
resources,
|
|
657
|
+
str_family,
|
|
658
|
+
parameters,
|
|
659
|
+
parts,
|
|
660
|
+
assessments,
|
|
661
|
+
objectives,
|
|
662
|
+
fedramp,
|
|
663
|
+
)
|
|
664
|
+
|
|
665
|
+
if isinstance(ctrl, list):
|
|
666
|
+
for cnt in ctrl:
|
|
667
|
+
oscal_controls = append_controls(
|
|
668
|
+
oscal_controls,
|
|
669
|
+
cnt,
|
|
670
|
+
resources,
|
|
671
|
+
str_family,
|
|
672
|
+
parameters,
|
|
673
|
+
parts,
|
|
674
|
+
assessments,
|
|
675
|
+
objectives,
|
|
676
|
+
fedramp,
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
# check for child controls/enhancements
|
|
680
|
+
if "controls" in ctrl:
|
|
681
|
+
child_ctrls = ctrl["controls"]
|
|
682
|
+
for child_ctrl in child_ctrls:
|
|
683
|
+
oscal_controls = append_controls(
|
|
684
|
+
oscal_controls,
|
|
685
|
+
child_ctrl,
|
|
686
|
+
resources,
|
|
687
|
+
str_family,
|
|
688
|
+
parameters,
|
|
689
|
+
parts,
|
|
690
|
+
assessments,
|
|
691
|
+
objectives,
|
|
692
|
+
fedramp,
|
|
693
|
+
)
|
|
694
|
+
|
|
695
|
+
# more unique processing for FedRAMP
|
|
696
|
+
if fedramp:
|
|
697
|
+
# arrays to hold processed fields
|
|
698
|
+
assessments = []
|
|
699
|
+
processed_objectives = []
|
|
700
|
+
# loop over each objective
|
|
701
|
+
for obj in objectives:
|
|
702
|
+
# replace response point language
|
|
703
|
+
if obj["description"].find("You must fill in this response point. ") > 0:
|
|
704
|
+
obj["description"] = obj["description"].replace("You must fill in this response point. ", "")
|
|
705
|
+
obj["description"] += " (REQUIRED)"
|
|
706
|
+
# put in the right bucket
|
|
707
|
+
if obj["objectiveType"] == "objective":
|
|
708
|
+
processed_objectives.append(obj)
|
|
709
|
+
else:
|
|
710
|
+
new_test = {
|
|
711
|
+
"id": 0,
|
|
712
|
+
"name": obj["name"],
|
|
713
|
+
"testType": "TEST",
|
|
714
|
+
"description": obj["description"],
|
|
715
|
+
"parentControl": obj["parentControl"],
|
|
716
|
+
}
|
|
717
|
+
assessments.append(new_test)
|
|
718
|
+
objectives = processed_objectives
|
|
719
|
+
|
|
720
|
+
# Write to file to visualize the output
|
|
721
|
+
save_data_to(
|
|
722
|
+
file=Path("./processing/families.json"),
|
|
723
|
+
data=families,
|
|
724
|
+
output_log=False,
|
|
725
|
+
)
|
|
726
|
+
|
|
727
|
+
# Write to file to visualize the output
|
|
728
|
+
save_data_to(
|
|
729
|
+
file=Path("./processing/controls.json"),
|
|
730
|
+
data=oscal_controls,
|
|
731
|
+
output_log=False,
|
|
732
|
+
)
|
|
733
|
+
|
|
734
|
+
# Write to file to visualize the output
|
|
735
|
+
save_data_to(
|
|
736
|
+
file=Path("./processing/parameters.json"),
|
|
737
|
+
data=parameters,
|
|
738
|
+
output_log=False,
|
|
739
|
+
)
|
|
740
|
+
|
|
741
|
+
# Write to file to visualize the output
|
|
742
|
+
save_data_to(
|
|
743
|
+
file=Path("./processing/parts.json"),
|
|
744
|
+
data=parts,
|
|
745
|
+
output_log=False,
|
|
746
|
+
)
|
|
747
|
+
|
|
748
|
+
# Write to file to visualize the output
|
|
749
|
+
save_data_to(
|
|
750
|
+
file=Path("./processing/tests.json"),
|
|
751
|
+
data=assessments,
|
|
752
|
+
output_log=False,
|
|
753
|
+
)
|
|
754
|
+
|
|
755
|
+
# Write to file to visualize the output
|
|
756
|
+
save_data_to(
|
|
757
|
+
file=Path("./processing/objectives.json"),
|
|
758
|
+
data=objectives,
|
|
759
|
+
output_log=False,
|
|
760
|
+
)
|
|
761
|
+
|
|
762
|
+
# output the items processed from the provided OSCAL catalog
|
|
763
|
+
logger.info(
|
|
764
|
+
"%s familys, %s controls, %s parameters, %s objectives %s parts & %s assessments processed from %s.",
|
|
765
|
+
len(families),
|
|
766
|
+
len(oscal_controls),
|
|
767
|
+
len(parameters),
|
|
768
|
+
len(objectives),
|
|
769
|
+
len(parts),
|
|
770
|
+
len(assessments),
|
|
771
|
+
get_file_name(file_name),
|
|
772
|
+
)
|
|
773
|
+
|
|
774
|
+
# use the progress object for the threaded process
|
|
775
|
+
with job_progress:
|
|
776
|
+
global new_regscale_controls
|
|
777
|
+
if oscal_controls:
|
|
778
|
+
# log the information
|
|
779
|
+
logger.debug("Posting %s OSCAL controls to RegScale.", len(oscal_controls))
|
|
780
|
+
# create task for job progress object
|
|
781
|
+
creating_controls = job_progress.add_task(
|
|
782
|
+
f"[#f8b737]Creating {len(oscal_controls)} controls in RegScale...",
|
|
783
|
+
total=len(oscal_controls),
|
|
784
|
+
)
|
|
785
|
+
# create threads to create controls
|
|
786
|
+
create_threads(
|
|
787
|
+
process=post_controls,
|
|
788
|
+
args=(
|
|
789
|
+
oscal_controls,
|
|
790
|
+
catalogue_id,
|
|
791
|
+
upload_flag,
|
|
792
|
+
creating_controls,
|
|
793
|
+
api,
|
|
794
|
+
),
|
|
795
|
+
thread_count=len(oscal_controls),
|
|
796
|
+
)
|
|
797
|
+
# log the outcome
|
|
798
|
+
logger.info(
|
|
799
|
+
"%s/%s OSCAL controls created in RegScale.",
|
|
800
|
+
len(new_regscale_controls),
|
|
801
|
+
len(oscal_controls),
|
|
802
|
+
)
|
|
803
|
+
# Write to file to visualize the output
|
|
804
|
+
save_data_to(
|
|
805
|
+
file=Path("./processing/mappedControls.json"),
|
|
806
|
+
data=new_controls,
|
|
807
|
+
output_log=False,
|
|
808
|
+
)
|
|
809
|
+
# Write to file to visualize the output
|
|
810
|
+
if upload_flag:
|
|
811
|
+
save_data_to(
|
|
812
|
+
file=Path("./processing/newControls.json"),
|
|
813
|
+
data=new_regscale_controls,
|
|
814
|
+
output_log=False,
|
|
815
|
+
)
|
|
816
|
+
else:
|
|
817
|
+
with open(f"processing{sep}newControls.json", "r", encoding="utf-8-sig") as infile:
|
|
818
|
+
new_regscale_controls = json.load(infile)
|
|
819
|
+
# only process if the controls exists to map to
|
|
820
|
+
if len(new_regscale_controls) > 0:
|
|
821
|
+
if parameters:
|
|
822
|
+
# log the information
|
|
823
|
+
logger.debug("Posting %s parameters to RegScale.", len(parameters))
|
|
824
|
+
# create task for analyzing child controls
|
|
825
|
+
posting_child_controls = job_progress.add_task(
|
|
826
|
+
f"[#ef5d23]Creating {len(parameters)} parameters in RegScale...",
|
|
827
|
+
total=len(parameters),
|
|
828
|
+
)
|
|
829
|
+
# create threads to post child controls
|
|
830
|
+
create_threads(
|
|
831
|
+
process=post_child_controls,
|
|
832
|
+
args=(
|
|
833
|
+
parameters,
|
|
834
|
+
new_regscale_controls,
|
|
835
|
+
params_flag,
|
|
836
|
+
posting_child_controls,
|
|
837
|
+
api,
|
|
838
|
+
),
|
|
839
|
+
thread_count=len(parameters),
|
|
840
|
+
)
|
|
841
|
+
# log the outcome
|
|
842
|
+
logger.info(
|
|
843
|
+
"%s/%s OSCAL parameters created in RegScale.",
|
|
844
|
+
len(new_params),
|
|
845
|
+
len(parameters),
|
|
846
|
+
)
|
|
847
|
+
|
|
848
|
+
# output the result
|
|
849
|
+
save_data_to(
|
|
850
|
+
file=Path("processing/newParameters.json"),
|
|
851
|
+
data=new_params,
|
|
852
|
+
output_log=False,
|
|
853
|
+
)
|
|
854
|
+
if assessments:
|
|
855
|
+
# log the information
|
|
856
|
+
logger.debug("Posting %s assessments to RegScale.", len(assessments))
|
|
857
|
+
# create task for creating assessments
|
|
858
|
+
assigning_tests = job_progress.add_task(
|
|
859
|
+
f"[#21a5bb]Creating {len(assessments)} assessments in RegScale...",
|
|
860
|
+
total=len(assessments),
|
|
861
|
+
)
|
|
862
|
+
# create threads to create tests
|
|
863
|
+
create_threads(
|
|
864
|
+
process=assign_control_tests,
|
|
865
|
+
args=(
|
|
866
|
+
assessments,
|
|
867
|
+
new_regscale_controls,
|
|
868
|
+
tests_flag,
|
|
869
|
+
assigning_tests,
|
|
870
|
+
api,
|
|
871
|
+
),
|
|
872
|
+
thread_count=len(assessments),
|
|
873
|
+
)
|
|
874
|
+
# output the result
|
|
875
|
+
save_data_to(
|
|
876
|
+
file=Path("./processing/newTests.json"),
|
|
877
|
+
data=new_tests,
|
|
878
|
+
output_log=False,
|
|
879
|
+
)
|
|
880
|
+
# log the outcome
|
|
881
|
+
logger.info(
|
|
882
|
+
"%s/%s assessments created in RegScale.",
|
|
883
|
+
len(new_tests),
|
|
884
|
+
len(assessments),
|
|
885
|
+
)
|
|
886
|
+
# process objectives based on FedRAMP flag
|
|
887
|
+
if not fedramp:
|
|
888
|
+
obj_parts = [
|
|
889
|
+
part
|
|
890
|
+
for part in parts
|
|
891
|
+
if part["objectiveType"] in ["objective", "assessment-objective"]
|
|
892
|
+
and part["description"] not in ["Determine if the organization:", "Determine if:"]
|
|
893
|
+
]
|
|
894
|
+
if obj_parts:
|
|
895
|
+
# log the information
|
|
896
|
+
logger.debug("Analyzing %s objectives for posting to RegScale.", len(parts))
|
|
897
|
+
# create task for creating objectives
|
|
898
|
+
creating_objectives = job_progress.add_task(
|
|
899
|
+
f"[#0866b4]Analyzing {len(parts)} OSCAL objectives for creation in RegScale...",
|
|
900
|
+
total=len(parts),
|
|
901
|
+
)
|
|
902
|
+
# create threads to loop through controls
|
|
903
|
+
create_threads(
|
|
904
|
+
process=create_objectives,
|
|
905
|
+
args=(
|
|
906
|
+
obj_parts,
|
|
907
|
+
new_regscale_controls,
|
|
908
|
+
objects_flag,
|
|
909
|
+
creating_objectives,
|
|
910
|
+
api,
|
|
911
|
+
),
|
|
912
|
+
thread_count=len(obj_parts),
|
|
913
|
+
)
|
|
914
|
+
# complete the task for creating objectives
|
|
915
|
+
job_progress.advance(creating_objectives, len(parts))
|
|
916
|
+
|
|
917
|
+
# log the outcome
|
|
918
|
+
logger.info("%s objectives created in RegScale.", len(new_objectives))
|
|
919
|
+
# output the result
|
|
920
|
+
save_data_to(
|
|
921
|
+
file=Path("./processing/newObjectives.json"),
|
|
922
|
+
data=new_objectives,
|
|
923
|
+
output_log=False,
|
|
924
|
+
)
|
|
925
|
+
else:
|
|
926
|
+
# log the information
|
|
927
|
+
logger.debug("Analyzing %s objectives for posting to RegScale.", len(objectives))
|
|
928
|
+
# create task for creating objectives
|
|
929
|
+
creating_objectives = job_progress.add_task(
|
|
930
|
+
f"[#0866b4]Creating {len(objectives)} objectives in RegScale...",
|
|
931
|
+
total=len(objectives),
|
|
932
|
+
)
|
|
933
|
+
# create threads to loop through controls
|
|
934
|
+
create_threads(
|
|
935
|
+
process=create_objectives,
|
|
936
|
+
args=(
|
|
937
|
+
objectives,
|
|
938
|
+
new_regscale_controls,
|
|
939
|
+
objects_flag,
|
|
940
|
+
creating_objectives,
|
|
941
|
+
api,
|
|
942
|
+
),
|
|
943
|
+
thread_count=len(objectives),
|
|
944
|
+
)
|
|
945
|
+
# complete the task for creating objectives
|
|
946
|
+
job_progress.advance(creating_objectives, len(objectives))
|
|
947
|
+
if deep_links_flag:
|
|
948
|
+
# process deep links
|
|
949
|
+
try:
|
|
950
|
+
logger.info(
|
|
951
|
+
"Retrieving all objectives for this catalogue # %i from RegScale (this might take a minute)...",
|
|
952
|
+
catalogue_id,
|
|
953
|
+
)
|
|
954
|
+
# extend the api timeout for this api call
|
|
955
|
+
api.timeout = 60
|
|
956
|
+
url_deep = f'{config["domain"]}/api/controlObjectives/getByCatalogue/{catalogue_id}'
|
|
957
|
+
obj_list_response = api.get(url_deep, headers={"Authorization": config["token"]})
|
|
958
|
+
obj_list = obj_list_response.json()
|
|
959
|
+
logger.info(
|
|
960
|
+
"%i total objectives now retrieved from RegScale for processing.",
|
|
961
|
+
len(obj_list),
|
|
962
|
+
)
|
|
963
|
+
except Exception:
|
|
964
|
+
error_and_exit("Unable to retrieve control objective information from RegScale.")
|
|
965
|
+
# log the information
|
|
966
|
+
logger.debug(
|
|
967
|
+
"Analyzing %s objectives for potential updating in RegScale.",
|
|
968
|
+
len(assessments),
|
|
969
|
+
)
|
|
970
|
+
# create task for creating objectives
|
|
971
|
+
updating_objectives = job_progress.add_task(
|
|
972
|
+
f"[#05d1b7]Analyzing {len(obj_list)} objectives in RegScale...",
|
|
973
|
+
total=len(obj_list),
|
|
974
|
+
)
|
|
975
|
+
# create threads to loop through controls
|
|
976
|
+
create_threads(
|
|
977
|
+
process=update_objectives,
|
|
978
|
+
args=(obj_list, parts, updating_objectives, api),
|
|
979
|
+
thread_count=len(obj_list),
|
|
980
|
+
)
|
|
981
|
+
# log the outcome
|
|
982
|
+
logger.info(
|
|
983
|
+
"%s objectives analyzed & %s objectives updated in RegScale.",
|
|
984
|
+
len(obj_list),
|
|
985
|
+
len(updates),
|
|
986
|
+
)
|
|
987
|
+
if errors:
|
|
988
|
+
# output the errors
|
|
989
|
+
save_data_to(
|
|
990
|
+
file=Path("./processing/errors.json"),
|
|
991
|
+
data=new_objectives,
|
|
992
|
+
)
|
|
993
|
+
if obj_to_controls:
|
|
994
|
+
# create task for creating objectives
|
|
995
|
+
inserting_controls_from_objectives = job_progress.add_task(
|
|
996
|
+
f"[#c42843]Creating {len(new_objectives)} control(s) from objectives..."
|
|
997
|
+
)
|
|
998
|
+
# Post Objectives as Controls
|
|
999
|
+
create_threads(
|
|
1000
|
+
process=post_alternative_controls,
|
|
1001
|
+
args=(
|
|
1002
|
+
new_objectives,
|
|
1003
|
+
parts,
|
|
1004
|
+
inserting_controls_from_objectives,
|
|
1005
|
+
api,
|
|
1006
|
+
),
|
|
1007
|
+
thread_count=len(new_objectives),
|
|
1008
|
+
)
|
|
1009
|
+
|
|
1010
|
+
|
|
1011
|
+
def post_controls(args: Tuple, thread: int) -> None:
|
|
1012
|
+
"""
|
|
1013
|
+
Function to analyze controls from OSCAL catalog and post them to RegScale while using threads
|
|
1014
|
+
|
|
1015
|
+
:param Tuple args: Tuple of args to use during the process
|
|
1016
|
+
:param int thread: Thread number of current thread
|
|
1017
|
+
:rtype: None
|
|
1018
|
+
"""
|
|
1019
|
+
# set up local variables from args passed
|
|
1020
|
+
controls, catalog_id, upload_flag, task, api = args
|
|
1021
|
+
|
|
1022
|
+
# set up RegScale URL
|
|
1023
|
+
url_sc = api.config["domain"] + SC_URL
|
|
1024
|
+
|
|
1025
|
+
# find which records should be executed by the current thread
|
|
1026
|
+
threads = thread_assignment(thread=thread, total_items=len(controls))
|
|
1027
|
+
|
|
1028
|
+
# iterate through the thread assignment items and process them
|
|
1029
|
+
for i in range(len(threads)):
|
|
1030
|
+
# set the recommendation for the thread for later use in the function
|
|
1031
|
+
control = controls[threads[i]]
|
|
1032
|
+
str_parts = strip_tag(
|
|
1033
|
+
control["parts"], "_obj"
|
|
1034
|
+
) # Strip objectives from the tag, we still need to keep them in the original parts list.
|
|
1035
|
+
# create each security control
|
|
1036
|
+
|
|
1037
|
+
description = str_parts.replace(
|
|
1038
|
+
"<ul><li>{{" + control["id"] + "_smt}} - </li><ul>",
|
|
1039
|
+
"<ul><li>{{" + control["id"] + "_smt}} - Control:</li><ul>",
|
|
1040
|
+
)
|
|
1041
|
+
|
|
1042
|
+
security_control = {
|
|
1043
|
+
"title": control["id"] + " - " + control["title"],
|
|
1044
|
+
"controlType": "Stand-Alone",
|
|
1045
|
+
"controlId": control["id"],
|
|
1046
|
+
"sortId": control["sortId"],
|
|
1047
|
+
"description": description + control["guidance"],
|
|
1048
|
+
"references": control["links"],
|
|
1049
|
+
"relatedControls": "",
|
|
1050
|
+
"subControls": "",
|
|
1051
|
+
"enhancements": control["enhancements"],
|
|
1052
|
+
"family": control["family"],
|
|
1053
|
+
"mappings": control["parameters"],
|
|
1054
|
+
"assessmentPlan": control["assessment"],
|
|
1055
|
+
"weight": 0,
|
|
1056
|
+
"practiceLevel": "",
|
|
1057
|
+
"catalogueID": catalog_id,
|
|
1058
|
+
"createdById": api.config["userId"],
|
|
1059
|
+
"lastUpdatedById": api.config["userId"],
|
|
1060
|
+
}
|
|
1061
|
+
# attempt to create the security control
|
|
1062
|
+
if upload_flag:
|
|
1063
|
+
try:
|
|
1064
|
+
# upload to RegScale
|
|
1065
|
+
|
|
1066
|
+
response = api.post(url=url_sc, json=security_control)
|
|
1067
|
+
json_response = response.json()
|
|
1068
|
+
logger.debug("\n\nSuccess - %s", security_control["title"])
|
|
1069
|
+
|
|
1070
|
+
# update id to the new control id
|
|
1071
|
+
security_control["id"] = json_response["id"]
|
|
1072
|
+
new_controls.append(security_control)
|
|
1073
|
+
|
|
1074
|
+
# add the new controls
|
|
1075
|
+
new_regscale_controls.append(json_response)
|
|
1076
|
+
except requests.exceptions.RequestException:
|
|
1077
|
+
logger.error("Unable to create security control %s,", security_control["title"])
|
|
1078
|
+
errors.append(security_control)
|
|
1079
|
+
else:
|
|
1080
|
+
# append the result
|
|
1081
|
+
new_regscale_controls.append(security_control)
|
|
1082
|
+
job_progress.update(task, advance=1)
|
|
1083
|
+
|
|
1084
|
+
|
|
1085
|
+
def extract_text(html_string: str, elem: str) -> dict:
|
|
1086
|
+
"""
|
|
1087
|
+
Function to return an object with a representation of a string formatted and cleaned
|
|
1088
|
+
|
|
1089
|
+
:param str html_string: A string of HTML
|
|
1090
|
+
:param str elem: An HTML tag type (ex. li)
|
|
1091
|
+
:return: A dictionary object with a representation of a string formatted and cleaned
|
|
1092
|
+
:rtype: dict
|
|
1093
|
+
"""
|
|
1094
|
+
result = {
|
|
1095
|
+
"key": "",
|
|
1096
|
+
"original_text": html_string,
|
|
1097
|
+
"clean_text": "",
|
|
1098
|
+
"extract_text": "",
|
|
1099
|
+
}
|
|
1100
|
+
pattern = r"{([^{{]*?)}"
|
|
1101
|
+
soup = BeautifulSoup(html_string, "html.parser")
|
|
1102
|
+
for elem in soup.find_all(elem):
|
|
1103
|
+
txt = elem.text.strip()
|
|
1104
|
+
result["clean_text"] = txt
|
|
1105
|
+
match = re.search(pattern, txt)
|
|
1106
|
+
if match:
|
|
1107
|
+
key = match.group(1)
|
|
1108
|
+
result["key"] = key
|
|
1109
|
+
dat_ix = txt.find(" -")
|
|
1110
|
+
result["extract_text"] = txt[dat_ix:]
|
|
1111
|
+
return result
|
|
1112
|
+
|
|
1113
|
+
|
|
1114
|
+
def strip_tag(html_string: str, sub_str_to_find: str) -> str:
|
|
1115
|
+
"""
|
|
1116
|
+
Function to strip an HTML tag from a string based on a substring
|
|
1117
|
+
|
|
1118
|
+
:param str html_string: A string of HTML
|
|
1119
|
+
:param str sub_str_to_find: A substring to search for
|
|
1120
|
+
:return: String with HTML tag removed
|
|
1121
|
+
:rtype: str
|
|
1122
|
+
"""
|
|
1123
|
+
result = html_string
|
|
1124
|
+
try:
|
|
1125
|
+
soup = BeautifulSoup(html_string, "html.parser")
|
|
1126
|
+
for tag in soup.find_all(lambda tag: tag.name == "li" and sub_str_to_find in tag.text):
|
|
1127
|
+
tag.extract()
|
|
1128
|
+
# Strip empty HTML tags
|
|
1129
|
+
for tag in soup.find_all(lambda tag: not tag.contents or tag.contents == [""]):
|
|
1130
|
+
tag.extract()
|
|
1131
|
+
for datx in soup.find_all():
|
|
1132
|
+
if len(datx.get_text(strip=True)) == 0:
|
|
1133
|
+
# Remove empty tags
|
|
1134
|
+
datx.extract()
|
|
1135
|
+
result = str(soup)
|
|
1136
|
+
except ValueError as vex:
|
|
1137
|
+
logger.error(vex)
|
|
1138
|
+
return result
|
|
1139
|
+
|
|
1140
|
+
|
|
1141
|
+
def post_alternative_controls(args: Tuple, thread: int) -> None:
|
|
1142
|
+
"""
|
|
1143
|
+
Function to post controls from objectives
|
|
1144
|
+
|
|
1145
|
+
:param Tuple args: Tuple of args to use during the process
|
|
1146
|
+
:param int thread: Thread number of current thread
|
|
1147
|
+
:rtype: None
|
|
1148
|
+
"""
|
|
1149
|
+
new_objs, parts, task, api = args
|
|
1150
|
+
|
|
1151
|
+
# find which records should be executed by the current thread
|
|
1152
|
+
threads = thread_assignment(thread=thread, total_items=len(new_objs))
|
|
1153
|
+
|
|
1154
|
+
# iterate through the thread assignment items and process them
|
|
1155
|
+
for i in range(len(threads)):
|
|
1156
|
+
obj = new_objs[threads[i]]
|
|
1157
|
+
# Please don't automate my job ChatGPT
|
|
1158
|
+
title = reformat_title(obj["name"])
|
|
1159
|
+
label = [part["part_label"] for part in parts if part["name"] == obj["name"]][0]
|
|
1160
|
+
# Create a Child Control for this special case
|
|
1161
|
+
security_control_id = obj["securityControlId"]
|
|
1162
|
+
ctrl_lookup = api.get(url=api.config["domain"] + f"{SC_URL}{security_control_id}").json()
|
|
1163
|
+
security_control = {
|
|
1164
|
+
"title": (
|
|
1165
|
+
f"objective: {label} - {obj['description']}" if title else "objective"
|
|
1166
|
+
), # f"{ctrl_lookup['controlId']} objective: {title}",
|
|
1167
|
+
"controlType": "Mapping", # Can also be Stand-Alone
|
|
1168
|
+
"controlId": ctrl_lookup["controlId"],
|
|
1169
|
+
"description": obj["description"],
|
|
1170
|
+
"references": ctrl_lookup["references"],
|
|
1171
|
+
"relatedControls": ctrl_lookup["id"],
|
|
1172
|
+
"subControls": "",
|
|
1173
|
+
"enhancements": "",
|
|
1174
|
+
"family": ctrl_lookup["family"],
|
|
1175
|
+
"mappings": "",
|
|
1176
|
+
"assessmentPlan": ctrl_lookup["assessmentPlan"],
|
|
1177
|
+
"weight": 0,
|
|
1178
|
+
"practiceLevel": "",
|
|
1179
|
+
"catalogueID": ctrl_lookup["catalogueID"],
|
|
1180
|
+
"createdById": api.config["userId"],
|
|
1181
|
+
"lastUpdatedById": api.config["userId"],
|
|
1182
|
+
}
|
|
1183
|
+
api.post(url=api.config["domain"] + SC_URL, json=security_control)
|
|
1184
|
+
job_progress.update(task, advance=1)
|
|
1185
|
+
|
|
1186
|
+
|
|
1187
|
+
def reformat_title(string: str) -> str:
|
|
1188
|
+
"""
|
|
1189
|
+
Function to reformat the title of a string
|
|
1190
|
+
|
|
1191
|
+
:param str string: A string to reformat
|
|
1192
|
+
:return: Reformatted string
|
|
1193
|
+
:rtype: str
|
|
1194
|
+
"""
|
|
1195
|
+
# strip everything before 'obj'
|
|
1196
|
+
new_string = None
|
|
1197
|
+
try:
|
|
1198
|
+
new_string = string.split("obj.")[1]
|
|
1199
|
+
except IndexError:
|
|
1200
|
+
new_string = string.split("obj")[1]
|
|
1201
|
+
new_string = new_string.replace("_", " ")
|
|
1202
|
+
new_string = (new_string.lstrip(".")).lstrip("-")
|
|
1203
|
+
return new_string
|
|
1204
|
+
|
|
1205
|
+
|
|
1206
|
+
def post_child_controls(args: Tuple, thread: int) -> None:
|
|
1207
|
+
"""
|
|
1208
|
+
Function to analyze child controls from OSCAL catalog and posts them to RegScale while using \
|
|
1209
|
+
threads
|
|
1210
|
+
|
|
1211
|
+
:param Tuple args: Tuple of args to use during the process
|
|
1212
|
+
:param int thread: Thread number of current thread
|
|
1213
|
+
:rtype: None
|
|
1214
|
+
"""
|
|
1215
|
+
# set up local variables from args passed
|
|
1216
|
+
parameters, regscale_controls, params_flag, task, api = args
|
|
1217
|
+
|
|
1218
|
+
# set up RegScale URL
|
|
1219
|
+
url_params = api.config["domain"] + "/api/controlParameters/"
|
|
1220
|
+
|
|
1221
|
+
# find which records should be executed by the current thread
|
|
1222
|
+
threads = thread_assignment(thread=thread, total_items=len(parameters))
|
|
1223
|
+
|
|
1224
|
+
# iterate through the thread assignment items and process them
|
|
1225
|
+
for i in range(len(threads)):
|
|
1226
|
+
# set the parameter for the thread for later use in the function
|
|
1227
|
+
parameter = parameters[threads[i]]
|
|
1228
|
+
|
|
1229
|
+
# find the parent control
|
|
1230
|
+
ctrl_lookup = next(
|
|
1231
|
+
(item for item in regscale_controls if (item["controlId"] == parameter["controlId"])),
|
|
1232
|
+
None,
|
|
1233
|
+
)
|
|
1234
|
+
if ctrl_lookup is None:
|
|
1235
|
+
logger.error(
|
|
1236
|
+
"Unable to locate %s for this parameter: %s.",
|
|
1237
|
+
parameter["controlId"],
|
|
1238
|
+
parameter["name"],
|
|
1239
|
+
)
|
|
1240
|
+
else:
|
|
1241
|
+
# create a new parameter to upload
|
|
1242
|
+
new_param = {
|
|
1243
|
+
"id": 0,
|
|
1244
|
+
"uuid": "",
|
|
1245
|
+
"text": parameter["value"],
|
|
1246
|
+
"dataType": "string",
|
|
1247
|
+
"parameterId": parameter["name"],
|
|
1248
|
+
"default": parameter["default"],
|
|
1249
|
+
"securityControlId": ctrl_lookup["id"],
|
|
1250
|
+
"archived": False,
|
|
1251
|
+
"createdById": api.config["userId"],
|
|
1252
|
+
"dateCreated": None,
|
|
1253
|
+
"lastUpdatedById": api.config["userId"],
|
|
1254
|
+
"dateLastUpdated": None,
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
# attempt to create the parameter
|
|
1258
|
+
if params_flag:
|
|
1259
|
+
try:
|
|
1260
|
+
# upload to RegScale
|
|
1261
|
+
response = api.post(url_params, json=new_param)
|
|
1262
|
+
response_data = response.json()
|
|
1263
|
+
logger.debug(
|
|
1264
|
+
"\n\nSuccess - %s parameter uploaded successfully.",
|
|
1265
|
+
new_param["parameterId"],
|
|
1266
|
+
)
|
|
1267
|
+
# update the id to the new parameter id
|
|
1268
|
+
new_param["id"] = response_data["id"]
|
|
1269
|
+
|
|
1270
|
+
# add the control to the new array
|
|
1271
|
+
new_params.append(new_param)
|
|
1272
|
+
except requests.exceptions.RequestException:
|
|
1273
|
+
logger.error("Unable to create parameter: %s.", new_param["parameterId"])
|
|
1274
|
+
errors.append(new_param)
|
|
1275
|
+
else:
|
|
1276
|
+
# add the control to the new array
|
|
1277
|
+
new_params.append(new_param)
|
|
1278
|
+
job_progress.update(task, advance=1)
|
|
1279
|
+
|
|
1280
|
+
|
|
1281
|
+
def assign_control_tests(args: Tuple, thread: int) -> None:
|
|
1282
|
+
"""
|
|
1283
|
+
Function to analyze match controls to assessments while using threads
|
|
1284
|
+
|
|
1285
|
+
:param Tuple args: Tuple of args to use during the process
|
|
1286
|
+
:param int thread: Thread number of current thread
|
|
1287
|
+
:rtype: None
|
|
1288
|
+
"""
|
|
1289
|
+
# set up local variables from args passed
|
|
1290
|
+
assessments, regscale_controls, fedramp, tests_flag, task, api = args
|
|
1291
|
+
|
|
1292
|
+
# set up RegScale URL
|
|
1293
|
+
url_tests = api.config["domain"] + "/api/controlTestPlans/"
|
|
1294
|
+
|
|
1295
|
+
# find which records should be executed by the current thread
|
|
1296
|
+
threads = thread_assignment(thread=thread, total_items=len(assessments))
|
|
1297
|
+
|
|
1298
|
+
# iterate through the thread assignment items and process them
|
|
1299
|
+
for i in range(len(threads)):
|
|
1300
|
+
# set the recommendation for the thread for later use in the function
|
|
1301
|
+
assessment = assessments[threads[i]]
|
|
1302
|
+
# find the parent control
|
|
1303
|
+
ctrl_lookup = next(
|
|
1304
|
+
(item for item in regscale_controls if (item["controlId"] == assessment["parentControl"])),
|
|
1305
|
+
None,
|
|
1306
|
+
)
|
|
1307
|
+
if ctrl_lookup is None:
|
|
1308
|
+
logger.error("Unable to locate %s for this test.", assessment["parentControl"])
|
|
1309
|
+
else:
|
|
1310
|
+
# create a new test to upload
|
|
1311
|
+
new_test = {
|
|
1312
|
+
"id": 0,
|
|
1313
|
+
"uuid": "",
|
|
1314
|
+
"test": f'{assessment["testType"]} - {assessment["description"]}',
|
|
1315
|
+
"testId": assessment["name"] if fedramp else str(uuid.uuid4()),
|
|
1316
|
+
"securityControlId": ctrl_lookup["id"],
|
|
1317
|
+
"archived": False,
|
|
1318
|
+
"createdById": api.config["userId"],
|
|
1319
|
+
"dateCreated": None,
|
|
1320
|
+
"lastUpdatedById": api.config["userId"],
|
|
1321
|
+
"dateLastUpdated": None,
|
|
1322
|
+
}
|
|
1323
|
+
# attempt to create the test
|
|
1324
|
+
if tests_flag:
|
|
1325
|
+
try:
|
|
1326
|
+
# upload to RegScale
|
|
1327
|
+
response = api.post(url_tests, json=new_test)
|
|
1328
|
+
json_response = response.json()
|
|
1329
|
+
logger.debug(
|
|
1330
|
+
"\n\nSuccess - %s - test uploaded successfully.",
|
|
1331
|
+
new_test["test"],
|
|
1332
|
+
)
|
|
1333
|
+
# update the id to the new test id
|
|
1334
|
+
new_test["id"] = json_response["id"]
|
|
1335
|
+
|
|
1336
|
+
# add the test to the new array
|
|
1337
|
+
new_tests.append(new_test)
|
|
1338
|
+
except requests.exceptions.RequestException:
|
|
1339
|
+
logger.error("Unable to create test: %s.", new_test["test"])
|
|
1340
|
+
errors.append(new_test)
|
|
1341
|
+
else:
|
|
1342
|
+
# add the test to the new array
|
|
1343
|
+
new_tests.append(new_test)
|
|
1344
|
+
job_progress.update(task, advance=1)
|
|
1345
|
+
|
|
1346
|
+
|
|
1347
|
+
def create_objectives(args: Tuple, thread: int) -> None:
|
|
1348
|
+
"""
|
|
1349
|
+
Function to create objectives from OSCAL catalog and post them to RegScale while using threads
|
|
1350
|
+
|
|
1351
|
+
:param Tuple args: Tuple of args to use during the process
|
|
1352
|
+
:param int thread: Thread number of current thread
|
|
1353
|
+
:rtype: None
|
|
1354
|
+
"""
|
|
1355
|
+
|
|
1356
|
+
# set up local variables from args passed
|
|
1357
|
+
(
|
|
1358
|
+
objectives,
|
|
1359
|
+
regscale_controls,
|
|
1360
|
+
objects_flag,
|
|
1361
|
+
task,
|
|
1362
|
+
api,
|
|
1363
|
+
) = args
|
|
1364
|
+
|
|
1365
|
+
# set up RegScale URL
|
|
1366
|
+
url_objs = api.config["domain"] + "/api/controlObjectives/"
|
|
1367
|
+
|
|
1368
|
+
# find which records should be executed by the current thread
|
|
1369
|
+
|
|
1370
|
+
obj_parts = list(objectives)
|
|
1371
|
+
threads = thread_assignment(thread=thread, total_items=len(obj_parts))
|
|
1372
|
+
|
|
1373
|
+
# iterate through the thread assignment items and process them
|
|
1374
|
+
for i in range(len(threads)):
|
|
1375
|
+
# set the recommendation for the thread for later use in the function
|
|
1376
|
+
objective: dict = obj_parts[threads[i]]
|
|
1377
|
+
|
|
1378
|
+
# find the parent control
|
|
1379
|
+
ctrl_lookup = next(
|
|
1380
|
+
(item for item in regscale_controls if (item["controlId"] == objective["parentControl"])),
|
|
1381
|
+
None,
|
|
1382
|
+
)
|
|
1383
|
+
if ctrl_lookup is None:
|
|
1384
|
+
logger.error(
|
|
1385
|
+
"Unable to locate %s for this objective/part: %s.",
|
|
1386
|
+
objective["parentControl"],
|
|
1387
|
+
objective["name"],
|
|
1388
|
+
)
|
|
1389
|
+
job_progress.update(task, advance=1)
|
|
1390
|
+
return
|
|
1391
|
+
|
|
1392
|
+
# create a new test to upload
|
|
1393
|
+
new_obj = {
|
|
1394
|
+
"id": 0,
|
|
1395
|
+
"uuid": "",
|
|
1396
|
+
"name": objective["name"],
|
|
1397
|
+
"description": objective["description"],
|
|
1398
|
+
"objectiveType": objective["objectiveType"],
|
|
1399
|
+
"otherId": "",
|
|
1400
|
+
"securityControlId": ctrl_lookup["id"],
|
|
1401
|
+
"parentObjectiveId": None,
|
|
1402
|
+
"archived": False,
|
|
1403
|
+
"createdById": api.config["userId"],
|
|
1404
|
+
"dateCreated": None,
|
|
1405
|
+
"lastUpdatedById": api.config["userId"],
|
|
1406
|
+
"dateLastUpdated": None,
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
# attempt to create the objective
|
|
1410
|
+
if objects_flag:
|
|
1411
|
+
try:
|
|
1412
|
+
# upload to RegScale
|
|
1413
|
+
response = api.post(url_objs, json=new_obj)
|
|
1414
|
+
json_response = response.json()
|
|
1415
|
+
logger.debug(
|
|
1416
|
+
"\n\nSuccess - %s - objective uploaded successfully.",
|
|
1417
|
+
new_obj["name"],
|
|
1418
|
+
)
|
|
1419
|
+
# try to update the id to the new control id
|
|
1420
|
+
try:
|
|
1421
|
+
new_obj["id"] = json_response["id"]
|
|
1422
|
+
except KeyError:
|
|
1423
|
+
continue
|
|
1424
|
+
|
|
1425
|
+
# add the objective to the new array
|
|
1426
|
+
new_objectives.append(new_obj)
|
|
1427
|
+
except requests.exceptions.RequestException as rex:
|
|
1428
|
+
logger.error("Unable to create objective: %s.\n%s", new_obj["name"], rex)
|
|
1429
|
+
errors.append(new_obj)
|
|
1430
|
+
else:
|
|
1431
|
+
# add the part to the new array
|
|
1432
|
+
new_objectives.append(new_obj)
|
|
1433
|
+
job_progress.update(task, advance=1)
|
|
1434
|
+
|
|
1435
|
+
|
|
1436
|
+
def update_objectives(args: Tuple, thread: int) -> None:
|
|
1437
|
+
"""
|
|
1438
|
+
Loop through each objective and see if it has a parent, if so,
|
|
1439
|
+
update parent ID and send update to RegScale while using threads
|
|
1440
|
+
|
|
1441
|
+
:param Tuple args: Tuple of args to use during the process
|
|
1442
|
+
:param int thread: Thread number of current thread
|
|
1443
|
+
:rtype: None
|
|
1444
|
+
"""
|
|
1445
|
+
# set up local variables from args passed
|
|
1446
|
+
objectives, parts, task, api = args
|
|
1447
|
+
|
|
1448
|
+
# set up RegScale URL
|
|
1449
|
+
url_objs = api.config["domain"] + "/api/controlObjectives/"
|
|
1450
|
+
|
|
1451
|
+
# find which records should be executed by the current thread
|
|
1452
|
+
threads = thread_assignment(thread=thread, total_items=len(objectives))
|
|
1453
|
+
|
|
1454
|
+
# iterate through the thread assignment items and process them
|
|
1455
|
+
for i in range(len(threads)):
|
|
1456
|
+
# set the recommendation for the thread for later use in the function
|
|
1457
|
+
objective = objectives[threads[i]]
|
|
1458
|
+
|
|
1459
|
+
# find the part by name
|
|
1460
|
+
part_lookup = next((item for item in parts if (item["name"] == objective["name"])), None)
|
|
1461
|
+
if part_lookup is not None:
|
|
1462
|
+
# see if the part has a parent
|
|
1463
|
+
if part_lookup["parentObjective"] != "":
|
|
1464
|
+
# lookup the parent objective from RegScale
|
|
1465
|
+
parent_lookup = next(
|
|
1466
|
+
(item for item in objectives if (item["name"] == part_lookup["parentObjective"])),
|
|
1467
|
+
None,
|
|
1468
|
+
)
|
|
1469
|
+
if parent_lookup is not None:
|
|
1470
|
+
logger.debug("Found Parent: %s.", parent_lookup["name"])
|
|
1471
|
+
# update the parent
|
|
1472
|
+
update_parent = parent_lookup["objective"]
|
|
1473
|
+
update_parent["parentObjectiveId"] = parent_lookup["id"]
|
|
1474
|
+
try:
|
|
1475
|
+
# upload to RegScale
|
|
1476
|
+
update_response = api.put(
|
|
1477
|
+
f'{url_objs}{update_parent["id"]}',
|
|
1478
|
+
json=update_parent,
|
|
1479
|
+
)
|
|
1480
|
+
logger.debug(
|
|
1481
|
+
"Success - %s - objective parent updated successfully.",
|
|
1482
|
+
update_parent["name"],
|
|
1483
|
+
)
|
|
1484
|
+
updates.append(update_response)
|
|
1485
|
+
except requests.exceptions.RequestException:
|
|
1486
|
+
logger.error(
|
|
1487
|
+
"Unable to update parent objective: %s.",
|
|
1488
|
+
update_parent["name"],
|
|
1489
|
+
)
|
|
1490
|
+
errors.append(update_parent)
|
|
1491
|
+
job_progress.update(task, advance=1)
|
|
1492
|
+
|
|
1493
|
+
|
|
1494
|
+
def process_fedramp_objectives(part: dict, str_obj: str, objectives: list, ctrl, str_name: Optional[str] = "") -> str:
|
|
1495
|
+
"""
|
|
1496
|
+
Function to handle processing of FedRAMP objectives
|
|
1497
|
+
|
|
1498
|
+
:param dict part: Part from OSCAL
|
|
1499
|
+
:param str str_obj: Concatenated objective string
|
|
1500
|
+
:param list objectives: A list of OSCAL objective strings
|
|
1501
|
+
:param ctrl: Parent security control
|
|
1502
|
+
:param str str_name: Name of the part
|
|
1503
|
+
:return: Processed FedRAMP objective object
|
|
1504
|
+
:rtype: str
|
|
1505
|
+
"""
|
|
1506
|
+
str_prose = part.get("prose", "")
|
|
1507
|
+
|
|
1508
|
+
str_name = part.get("name", "") if not str_name else str_name
|
|
1509
|
+
if str_name == "statement":
|
|
1510
|
+
str_obj += f"{str_prose} "
|
|
1511
|
+
elif str_name == "item" or str_name == "objective":
|
|
1512
|
+
if "props" in part and str_name != "objective":
|
|
1513
|
+
str_obj += f"{part['props'][0]['value']} "
|
|
1514
|
+
str_obj += f"{str_prose} "
|
|
1515
|
+
str_type = "assessment" if str_name == "objective" else "objective"
|
|
1516
|
+
|
|
1517
|
+
if sub_parts := part.get("parts", []):
|
|
1518
|
+
for sub_part in sub_parts:
|
|
1519
|
+
str_obj2 = str_obj
|
|
1520
|
+
_ = process_fedramp_objectives(sub_part, str_obj2, objectives, ctrl, str_name)
|
|
1521
|
+
else:
|
|
1522
|
+
if str_obj.rstrip() != "":
|
|
1523
|
+
new_obj = {
|
|
1524
|
+
"id": 0,
|
|
1525
|
+
"name": part["id"],
|
|
1526
|
+
"part_id": ctrl["id"],
|
|
1527
|
+
"objectiveType": str_type,
|
|
1528
|
+
"description": str_obj,
|
|
1529
|
+
"parentControl": ctrl["id"],
|
|
1530
|
+
"parentObjective": "",
|
|
1531
|
+
}
|
|
1532
|
+
objectives.append(new_obj)
|
|
1533
|
+
return str_obj.rstrip()
|
|
1534
|
+
|
|
1535
|
+
|
|
1536
|
+
def process_objectives(objs: list, parts: list, ctrl: Union[list, dict], parent_id: int, fedramp: bool) -> str:
|
|
1537
|
+
"""
|
|
1538
|
+
Function for recursively working through objectives and formatting it as HTML description
|
|
1539
|
+
|
|
1540
|
+
:param list objs: List of RegScale object
|
|
1541
|
+
:param list parts: Parts of the object
|
|
1542
|
+
:param Union[list, dict] ctrl: Controls for the object
|
|
1543
|
+
:param int parent_id: Parent id of the Object
|
|
1544
|
+
:param bool fedramp: Flag if catalog is a FedRAMP catalog
|
|
1545
|
+
:return: HTML formatted string of object
|
|
1546
|
+
:rtype: str
|
|
1547
|
+
"""
|
|
1548
|
+
str_obj = "<ul>"
|
|
1549
|
+
# loop through parts/objectives recursively
|
|
1550
|
+
for obj in objs:
|
|
1551
|
+
# check prose
|
|
1552
|
+
str_prose = obj.get("prose", "")
|
|
1553
|
+
|
|
1554
|
+
# check name
|
|
1555
|
+
str_name = obj.get("name", "")
|
|
1556
|
+
|
|
1557
|
+
# create the new part
|
|
1558
|
+
part = {
|
|
1559
|
+
"id": 0,
|
|
1560
|
+
"part_id": obj["id"].split("_")[0],
|
|
1561
|
+
"part_label": obj["props"][0]["value"] if "props" in obj else None,
|
|
1562
|
+
"name": obj["id"],
|
|
1563
|
+
"objectiveType": str_name if "objective" not in str_name else "objective",
|
|
1564
|
+
"description": str_prose,
|
|
1565
|
+
"parentControl": ctrl["id"],
|
|
1566
|
+
"parentObjective": parent_id,
|
|
1567
|
+
}
|
|
1568
|
+
if "obj" in obj["id"] or "item" in obj["name"] or "overview" in obj["name"]:
|
|
1569
|
+
parts.append(part)
|
|
1570
|
+
str_obj += LI + "{{" + obj["id"] + "}}"
|
|
1571
|
+
if "prose" in obj:
|
|
1572
|
+
str_obj += " - " + str_prose
|
|
1573
|
+
str_obj += LI_CLOSE
|
|
1574
|
+
if "parts" in obj:
|
|
1575
|
+
str_obj += process_objectives(obj["parts"], parts, ctrl, obj["id"], fedramp)
|
|
1576
|
+
str_obj += UL_CLOSE
|
|
1577
|
+
return str_obj
|
|
1578
|
+
|
|
1579
|
+
|
|
1580
|
+
def process_control(
|
|
1581
|
+
ctrl: Union[list, dict],
|
|
1582
|
+
resources: list,
|
|
1583
|
+
str_family: str,
|
|
1584
|
+
parameters: list,
|
|
1585
|
+
parts: list,
|
|
1586
|
+
assessments: list,
|
|
1587
|
+
objectives: list,
|
|
1588
|
+
fedramp: bool,
|
|
1589
|
+
) -> dict:
|
|
1590
|
+
"""
|
|
1591
|
+
Function to process each control and formats it as a dictionary
|
|
1592
|
+
|
|
1593
|
+
:param Union[list, dict] ctrl: RegScale control
|
|
1594
|
+
:param list resources: resources of control
|
|
1595
|
+
:param str str_family: Family of control
|
|
1596
|
+
:param list parameters: Parameters for the control
|
|
1597
|
+
:param list parts: Parts of the control
|
|
1598
|
+
:param list assessments: Assessments belonging to the control
|
|
1599
|
+
:param list objectives: A list of OSCAL objective strings
|
|
1600
|
+
:param bool fedramp: Boolean - is a FedRAMP catalog
|
|
1601
|
+
:return: Dictionary of new control
|
|
1602
|
+
:rtype: dict
|
|
1603
|
+
"""
|
|
1604
|
+
# see if parameters exist
|
|
1605
|
+
if "params" in ctrl:
|
|
1606
|
+
# loop through each parameter
|
|
1607
|
+
for param in ctrl["params"]:
|
|
1608
|
+
# create a new parameter object
|
|
1609
|
+
p_new = {
|
|
1610
|
+
"name": param["id"],
|
|
1611
|
+
"value": "",
|
|
1612
|
+
"paramType": "",
|
|
1613
|
+
"default": "",
|
|
1614
|
+
"controlId": ctrl["id"],
|
|
1615
|
+
}
|
|
1616
|
+
# process basic label
|
|
1617
|
+
if "label" in param:
|
|
1618
|
+
p_new["paramType"] = "text"
|
|
1619
|
+
p_new["value"] = param["label"]
|
|
1620
|
+
else:
|
|
1621
|
+
# initialize
|
|
1622
|
+
str_params = "Select ("
|
|
1623
|
+
# process select types
|
|
1624
|
+
if "select" in param:
|
|
1625
|
+
select = param["select"]
|
|
1626
|
+
if "how-many" in select:
|
|
1627
|
+
str_params += select["how-many"]
|
|
1628
|
+
p_new["paramType"] = "how-many"
|
|
1629
|
+
if "choice" in select:
|
|
1630
|
+
p_new["paramType"] = "choice"
|
|
1631
|
+
str_params += "select) - "
|
|
1632
|
+
for cho in select["choice"]:
|
|
1633
|
+
str_params += cho + ", "
|
|
1634
|
+
p_new["value"] = str_params
|
|
1635
|
+
|
|
1636
|
+
# check for default
|
|
1637
|
+
if "constraints" in param:
|
|
1638
|
+
for c in param["constraints"]:
|
|
1639
|
+
p_new["default"] += c["description"] + "; "
|
|
1640
|
+
|
|
1641
|
+
# add to the array
|
|
1642
|
+
parameters.append(p_new)
|
|
1643
|
+
|
|
1644
|
+
# get enhancements
|
|
1645
|
+
str_enhance = ""
|
|
1646
|
+
if "controls" in ctrl:
|
|
1647
|
+
child_enhc = ctrl["controls"]
|
|
1648
|
+
str_enhance += "<strong>Enhancements</strong><br/><br/>"
|
|
1649
|
+
str_enhance += "<ul>"
|
|
1650
|
+
for che in child_enhc:
|
|
1651
|
+
str_enhance += LI + "{{" + che["id"] + "}} - " + che["title"] + LI_CLOSE
|
|
1652
|
+
str_enhance += UL_CLOSE
|
|
1653
|
+
|
|
1654
|
+
# process control links
|
|
1655
|
+
int_link = 1
|
|
1656
|
+
str_links = ""
|
|
1657
|
+
if "links" in ctrl:
|
|
1658
|
+
for link in ctrl["links"]:
|
|
1659
|
+
# lookup the OSCAL control to enrich the data
|
|
1660
|
+
link_lookup = next(
|
|
1661
|
+
(item for item in resources if ("#" + item["uuid"]) == link["href"]),
|
|
1662
|
+
None,
|
|
1663
|
+
)
|
|
1664
|
+
if link_lookup is not None:
|
|
1665
|
+
str_links += (
|
|
1666
|
+
str(int_link) + ") " + link_lookup["title"] + " (OSCAL ID: " + link_lookup["uuid"] + ")<br/>"
|
|
1667
|
+
)
|
|
1668
|
+
int_link += 1
|
|
1669
|
+
else:
|
|
1670
|
+
str_links += link["href"] + "<br/>"
|
|
1671
|
+
|
|
1672
|
+
# process parts
|
|
1673
|
+
part_info = process_parts(ctrl, parts, assessments, objectives, fedramp)
|
|
1674
|
+
|
|
1675
|
+
# process sort Id if provided (FedRAMP specific)
|
|
1676
|
+
str_sort_id = ctrl["id"]
|
|
1677
|
+
if "props" in ctrl:
|
|
1678
|
+
for p in ctrl["props"]:
|
|
1679
|
+
if p["name"] == "sort-id":
|
|
1680
|
+
str_sort_id = p["value"]
|
|
1681
|
+
|
|
1682
|
+
# add control
|
|
1683
|
+
new_ctrl = {
|
|
1684
|
+
"id": ctrl["id"],
|
|
1685
|
+
"title": ctrl["title"],
|
|
1686
|
+
"sortId": str_sort_id,
|
|
1687
|
+
"family": (str_family if package_version.parse(schema) <= package_version.parse("1.0.2") else ctrl["class"]),
|
|
1688
|
+
"links": str_links,
|
|
1689
|
+
"parameters": "",
|
|
1690
|
+
"parts": part_info["parts"],
|
|
1691
|
+
"assessment": part_info["assessments"],
|
|
1692
|
+
"guidance": part_info["guidance"],
|
|
1693
|
+
"enhancements": str_enhance,
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
# return the result
|
|
1697
|
+
return new_ctrl
|
|
1698
|
+
|
|
1699
|
+
|
|
1700
|
+
def process_parts(ctrl: Union[list, dict], parts: list, assessments: list, objectives: list, fedramp: bool) -> dict:
|
|
1701
|
+
"""
|
|
1702
|
+
Function to format control
|
|
1703
|
+
|
|
1704
|
+
:param Union[list, dict] ctrl: RegScale control
|
|
1705
|
+
:param list parts: Parts of the control
|
|
1706
|
+
:param list assessments: Assessments of the control
|
|
1707
|
+
:param list objectives: A list of OSCAL objective strings
|
|
1708
|
+
:param bool fedramp: Boolean - is a FedRAMP catalog
|
|
1709
|
+
:return: Formatted dictionary
|
|
1710
|
+
:rtype: dict
|
|
1711
|
+
"""
|
|
1712
|
+
# process parts
|
|
1713
|
+
if "parts" in ctrl:
|
|
1714
|
+
# initialize
|
|
1715
|
+
str_parts = ""
|
|
1716
|
+
str_guidance = ""
|
|
1717
|
+
str_assessment = ""
|
|
1718
|
+
|
|
1719
|
+
# create text field for human display
|
|
1720
|
+
str_parts += "<ul>"
|
|
1721
|
+
for dat in ctrl["parts"]:
|
|
1722
|
+
if (
|
|
1723
|
+
("id" in dat)
|
|
1724
|
+
and (dat["name"].startswith("assessment") is False and dat["name"].startswith("assess") is False)
|
|
1725
|
+
) or (
|
|
1726
|
+
("id" in dat)
|
|
1727
|
+
and dat["name"]
|
|
1728
|
+
in [
|
|
1729
|
+
"item",
|
|
1730
|
+
"assessment-objective",
|
|
1731
|
+
"statement",
|
|
1732
|
+
"objective",
|
|
1733
|
+
"overview",
|
|
1734
|
+
]
|
|
1735
|
+
):
|
|
1736
|
+
# check prose
|
|
1737
|
+
str_prose = ""
|
|
1738
|
+
if "prose" in dat:
|
|
1739
|
+
str_prose = dat["prose"]
|
|
1740
|
+
|
|
1741
|
+
# check name
|
|
1742
|
+
str_name = ""
|
|
1743
|
+
if "name" in dat:
|
|
1744
|
+
str_name = dat["name"]
|
|
1745
|
+
|
|
1746
|
+
# create the new part
|
|
1747
|
+
part = {
|
|
1748
|
+
"id": 0,
|
|
1749
|
+
"name": dat["id"],
|
|
1750
|
+
"part_id": dat["id"].split("_")[0],
|
|
1751
|
+
"objectiveType": str_name,
|
|
1752
|
+
"description": (str_prose if str_prose else "Determine if the organization:"),
|
|
1753
|
+
"parentControl": ctrl["id"],
|
|
1754
|
+
"parentObjective": "",
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
# process statements and objectives
|
|
1758
|
+
if str_name in [
|
|
1759
|
+
"assessment-objective",
|
|
1760
|
+
"statement",
|
|
1761
|
+
"objective",
|
|
1762
|
+
"overview",
|
|
1763
|
+
]:
|
|
1764
|
+
parts.append(part)
|
|
1765
|
+
try:
|
|
1766
|
+
str_parts += LI + "{{" + dat["id"] + "}} - " + str_prose + LI_CLOSE
|
|
1767
|
+
except Exception:
|
|
1768
|
+
logger.error("Unable to parse part - %s.", dat["id"])
|
|
1769
|
+
if "parts" in dat:
|
|
1770
|
+
str_parts += process_objectives(dat["parts"], parts, ctrl, dat["id"], fedramp)
|
|
1771
|
+
|
|
1772
|
+
# FedRAMP specific processing
|
|
1773
|
+
if fedramp:
|
|
1774
|
+
process_fedramp_objectives(dat, "", objectives, ctrl)
|
|
1775
|
+
|
|
1776
|
+
# process guidance
|
|
1777
|
+
if dat["name"] in ["guidance", "overview"]:
|
|
1778
|
+
str_guidance = "<ul><li>Guidance</li>"
|
|
1779
|
+
if "prose" in dat:
|
|
1780
|
+
str_guidance += "<ul>"
|
|
1781
|
+
str_guidance += LI + dat["prose"] + LI_CLOSE
|
|
1782
|
+
str_guidance += UL_CLOSE
|
|
1783
|
+
if "links" in dat:
|
|
1784
|
+
str_guidance += "<ul>"
|
|
1785
|
+
for lkp in dat["links"]:
|
|
1786
|
+
str_guidance += LI + lkp["href"] + ", " + lkp["rel"] + LI_CLOSE
|
|
1787
|
+
str_guidance += UL_CLOSE
|
|
1788
|
+
str_guidance += UL_CLOSE
|
|
1789
|
+
else:
|
|
1790
|
+
# process assessments
|
|
1791
|
+
process_assessments(dat, ctrl, assessments)
|
|
1792
|
+
|
|
1793
|
+
str_parts += UL_CLOSE
|
|
1794
|
+
else:
|
|
1795
|
+
# no parts - set default values
|
|
1796
|
+
str_parts = ""
|
|
1797
|
+
str_guidance = ""
|
|
1798
|
+
str_assessment = ""
|
|
1799
|
+
|
|
1800
|
+
# return the result
|
|
1801
|
+
part_info = {
|
|
1802
|
+
"parts": str_parts,
|
|
1803
|
+
"guidance": str_guidance,
|
|
1804
|
+
"assessments": str_assessment,
|
|
1805
|
+
}
|
|
1806
|
+
return part_info
|
|
1807
|
+
|
|
1808
|
+
|
|
1809
|
+
def process_assessments(dat: dict, ctrl: dict, assessments: list) -> None:
|
|
1810
|
+
"""
|
|
1811
|
+
Process assessment data
|
|
1812
|
+
|
|
1813
|
+
:param dict dat: Data to process
|
|
1814
|
+
:param dict ctrl: Controls
|
|
1815
|
+
:param list assessments: list of assessments
|
|
1816
|
+
:rtype: None
|
|
1817
|
+
"""
|
|
1818
|
+
# process assessments
|
|
1819
|
+
if dat["name"].startswith("assessment") is True:
|
|
1820
|
+
# see if a lower level objective that has prose
|
|
1821
|
+
if "prose" in dat:
|
|
1822
|
+
# create new assessment objective
|
|
1823
|
+
ast = {
|
|
1824
|
+
"id": 0,
|
|
1825
|
+
"name": dat["id"],
|
|
1826
|
+
"testType": dat["name"],
|
|
1827
|
+
"description": dat["prose"],
|
|
1828
|
+
"parentControl": ctrl["id"],
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
# see if it has any child tests
|
|
1832
|
+
if "parts" in dat:
|
|
1833
|
+
if len(dat["parts"]) > 0:
|
|
1834
|
+
for item in dat["parts"]:
|
|
1835
|
+
process_assessments(item, ctrl, assessments)
|
|
1836
|
+
else:
|
|
1837
|
+
# check the id
|
|
1838
|
+
str_part_id = ""
|
|
1839
|
+
if "id" in dat:
|
|
1840
|
+
str_part_id = dat["id"]
|
|
1841
|
+
else:
|
|
1842
|
+
str_part_id = str(uuid.uuid4())
|
|
1843
|
+
|
|
1844
|
+
# handle methods
|
|
1845
|
+
ast = {
|
|
1846
|
+
"id": 0,
|
|
1847
|
+
"name": str_part_id,
|
|
1848
|
+
"testType": "",
|
|
1849
|
+
"description": "",
|
|
1850
|
+
"parentControl": ctrl["id"],
|
|
1851
|
+
}
|
|
1852
|
+
props_data = dat.get("props", [{}])
|
|
1853
|
+
if "value" in props_data[0]:
|
|
1854
|
+
ast["testType"] = props_data[0]["value"]
|
|
1855
|
+
parts_data = dat.get("parts", [{}])
|
|
1856
|
+
if "prose" in parts_data[0]:
|
|
1857
|
+
ast["description"] = parts_data[0]["prose"]
|
|
1858
|
+
|
|
1859
|
+
# add test of the array
|
|
1860
|
+
if ast["description"] != "":
|
|
1861
|
+
assessments.append(ast)
|
|
1862
|
+
elif dat["name"].startswith("assess") is True:
|
|
1863
|
+
# check the id
|
|
1864
|
+
str_part_id = ""
|
|
1865
|
+
if "id" in dat:
|
|
1866
|
+
str_part_id = dat["id"]
|
|
1867
|
+
else:
|
|
1868
|
+
str_part_id = str(uuid.uuid4())
|
|
1869
|
+
|
|
1870
|
+
# handle methods
|
|
1871
|
+
ast = {
|
|
1872
|
+
"id": 0,
|
|
1873
|
+
"name": str_part_id,
|
|
1874
|
+
"testType": "",
|
|
1875
|
+
"description": "",
|
|
1876
|
+
"parentControl": ctrl["id"],
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
# check test type
|
|
1880
|
+
str_test_type = "TEST"
|
|
1881
|
+
props_data = dat.get("props", [{}])
|
|
1882
|
+
if "value" in props_data[0]:
|
|
1883
|
+
str_test_type = props_data[0]["value"]
|
|
1884
|
+
ast["testType"] = str_test_type
|
|
1885
|
+
|
|
1886
|
+
# get the description
|
|
1887
|
+
str_description = ""
|
|
1888
|
+
parts_data = dat.get("parts", [{}])
|
|
1889
|
+
if "prose" in parts_data[0]:
|
|
1890
|
+
str_description = parts_data[0]["prose"]
|
|
1891
|
+
ast["description"] = str_description
|
|
1892
|
+
|
|
1893
|
+
# add test of the array
|
|
1894
|
+
if ast["description"] != "":
|
|
1895
|
+
assessments.append(ast)
|
|
1896
|
+
|
|
1897
|
+
|
|
1898
|
+
def oscal_version(metadata: dict) -> str:
|
|
1899
|
+
"""
|
|
1900
|
+
Determine the oscal base version
|
|
1901
|
+
|
|
1902
|
+
:param dict metadata: The metadata from OSCAL
|
|
1903
|
+
:raises ValueError: CLI support statement
|
|
1904
|
+
:return: The schema version
|
|
1905
|
+
:rtype: str
|
|
1906
|
+
"""
|
|
1907
|
+
supported_versions = ["1.0.0", "1.0.2", "1.0.4", "1.1.1", "1.1.2"]
|
|
1908
|
+
oscal_schema = metadata["oscal-version"]
|
|
1909
|
+
if oscal_schema not in supported_versions:
|
|
1910
|
+
raise ValueError(
|
|
1911
|
+
f"The RegScale CLI does not support OSCAL version {oscal_schema}, only "
|
|
1912
|
+
f"versions {', '.join(supported_versions)} are supported."
|
|
1913
|
+
)
|
|
1914
|
+
logger.info("Oscal Version: %s", oscal_schema)
|
|
1915
|
+
return oscal_schema
|
|
1916
|
+
|
|
1917
|
+
|
|
1918
|
+
def append_controls(
|
|
1919
|
+
oscal_controls: list,
|
|
1920
|
+
ctrl: Union[list, dict],
|
|
1921
|
+
resources: list,
|
|
1922
|
+
str_family: str,
|
|
1923
|
+
parameters: list,
|
|
1924
|
+
parts: list,
|
|
1925
|
+
assessments: list,
|
|
1926
|
+
objectives: list,
|
|
1927
|
+
fedramp: bool,
|
|
1928
|
+
) -> list[dict]:
|
|
1929
|
+
"""
|
|
1930
|
+
Process and append controls to list
|
|
1931
|
+
|
|
1932
|
+
:param list oscal_controls: A list of oscal control dictionaries.
|
|
1933
|
+
:param Union[list, dict] ctrl: An OSCAL control dictionary or list
|
|
1934
|
+
:param list resources: resources of control
|
|
1935
|
+
:param str str_family: The name of the control family
|
|
1936
|
+
:param list parameters: A list of parameters dictionaries
|
|
1937
|
+
:param list parts: A list of OSCAL part strings.
|
|
1938
|
+
:param list assessments: A list of OSCAL assesment strings
|
|
1939
|
+
:param list objectives: A list of OSCAL objective strings
|
|
1940
|
+
:param bool fedramp: Boolean - is a FedRAMP catalog
|
|
1941
|
+
:return: list[dict]
|
|
1942
|
+
:rtype: list[dict]
|
|
1943
|
+
"""
|
|
1944
|
+
new_ctrl = process_control(ctrl, resources, str_family, parameters, parts, assessments, objectives, fedramp)
|
|
1945
|
+
oscal_controls.append(new_ctrl)
|
|
1946
|
+
return oscal_controls
|