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,1124 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# pylint: disable=C0415
|
|
4
|
+
"""standard python imports"""
|
|
5
|
+
import json
|
|
6
|
+
import math
|
|
7
|
+
import re
|
|
8
|
+
from collections import Counter
|
|
9
|
+
from concurrent.futures import as_completed
|
|
10
|
+
from concurrent.futures.thread import ThreadPoolExecutor
|
|
11
|
+
from typing import Dict, List, Literal, Optional, Tuple
|
|
12
|
+
from urllib.parse import urljoin
|
|
13
|
+
|
|
14
|
+
import click
|
|
15
|
+
|
|
16
|
+
from regscale.core.app.api import Api
|
|
17
|
+
from regscale.core.app.utils.app_utils import create_progress_object, error_and_exit, get_current_datetime
|
|
18
|
+
from regscale.core.utils.graphql import GraphQLQuery
|
|
19
|
+
from regscale.integrations.public.fedramp.parts_mapper import PartMapper
|
|
20
|
+
from regscale.integrations.public.fedramp.ssp_logger import SSPLogger
|
|
21
|
+
from regscale.models import ControlObjective, ImplementationObjective
|
|
22
|
+
from regscale.models.regscale_models import (
|
|
23
|
+
ControlImplementation,
|
|
24
|
+
File,
|
|
25
|
+
LeveragedAuthorization,
|
|
26
|
+
SecurityControl,
|
|
27
|
+
SecurityPlan,
|
|
28
|
+
)
|
|
29
|
+
from regscale.models.regscale_models.control_implementation import ControlImplementationStatus
|
|
30
|
+
|
|
31
|
+
logger = SSPLogger()
|
|
32
|
+
part_mapper_rev5 = PartMapper()
|
|
33
|
+
part_mapper_rev4 = PartMapper()
|
|
34
|
+
progress = create_progress_object()
|
|
35
|
+
|
|
36
|
+
SERVICE_PROVIDER_CORPORATE = "Service Provider Corporate"
|
|
37
|
+
SERVICE_PROVIDER_SYSTEM_SPECIFIC = "Service Provider System Specific"
|
|
38
|
+
SERVICE_PROVIDER_HYBRID = "Service Provider Hybrid"
|
|
39
|
+
PROVIDER_SYSTEM_SPECIFIC = "Provider (System Specific)"
|
|
40
|
+
CUSTOMER_PROVIDED = "Provided by Customer"
|
|
41
|
+
CUSTOMER_CONFIGURED = "Customer Configured"
|
|
42
|
+
CONFIGURED_BY_CUSTOMER = "Configured by Customer"
|
|
43
|
+
NOT_IMPLEMENTED = ControlImplementationStatus.NotImplemented.value
|
|
44
|
+
PARTIALLY_IMPLEMENTED = ControlImplementationStatus.PartiallyImplemented.value
|
|
45
|
+
CONTROL_ID = "Control ID"
|
|
46
|
+
ALT_IMPLEMENTATION = "Alternate Implementation"
|
|
47
|
+
CAN_BE_INHERITED_CSP = "Can Be Inherited from CSP"
|
|
48
|
+
IMPACT_LEVEL = "Impact Level"
|
|
49
|
+
SYSTEM_NAME = "System Name"
|
|
50
|
+
CSP = "CSP"
|
|
51
|
+
|
|
52
|
+
STATUS_MAPPING = {
|
|
53
|
+
"Implemented": ControlImplementationStatus.Implemented,
|
|
54
|
+
PARTIALLY_IMPLEMENTED: ControlImplementationStatus.PartiallyImplemented,
|
|
55
|
+
ControlImplementationStatus.Planned.value: ControlImplementationStatus.Planned,
|
|
56
|
+
"N/A": ControlImplementationStatus.NA,
|
|
57
|
+
"Alternative Implementation": ControlImplementationStatus.Alternative,
|
|
58
|
+
ALT_IMPLEMENTATION: ControlImplementationStatus.Alternative,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
RESPONSIBILITY_MAP = {
|
|
62
|
+
# Original keys
|
|
63
|
+
"SERVICE_PROVIDER_CORPORATE": "Provider",
|
|
64
|
+
SERVICE_PROVIDER_SYSTEM_SPECIFIC: PROVIDER_SYSTEM_SPECIFIC,
|
|
65
|
+
SERVICE_PROVIDER_HYBRID: "Hybrid",
|
|
66
|
+
CUSTOMER_PROVIDED: "Customer",
|
|
67
|
+
CONFIGURED_BY_CUSTOMER: CUSTOMER_CONFIGURED,
|
|
68
|
+
"Shared": "Shared",
|
|
69
|
+
"Inherited": "Inherited",
|
|
70
|
+
# Boolean keys
|
|
71
|
+
"bServiceProviderCorporate": "Provider",
|
|
72
|
+
"bServiceProviderSystemSpecific": PROVIDER_SYSTEM_SPECIFIC,
|
|
73
|
+
"bServiceProviderHybrid": "Hybrid",
|
|
74
|
+
"bProvidedByCustomer": "Customer",
|
|
75
|
+
"bConfiguredByCustomer": CUSTOMER_CONFIGURED,
|
|
76
|
+
"bShared": "Shared",
|
|
77
|
+
"bInherited": "Inherited",
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def transform_control(control: str) -> str:
|
|
82
|
+
"""
|
|
83
|
+
Function to parse the control string and transform it to the RegScale format
|
|
84
|
+
ex: AC-1 (a) -> ac-1.a or AC-6 (10) -> ac-6.10
|
|
85
|
+
|
|
86
|
+
:param str control: Control ID as a string
|
|
87
|
+
:return: Transformed control ID to match RegScale control ID format
|
|
88
|
+
:rtype: str
|
|
89
|
+
"""
|
|
90
|
+
# Use regex to match the pattern and capture the parts
|
|
91
|
+
match = re.match(r"([A-Za-z]+)-(\d+)\s\((\d+|[a-z])\)", control)
|
|
92
|
+
if match:
|
|
93
|
+
control_name = match.group(1).lower()
|
|
94
|
+
control_number = match.group(2)
|
|
95
|
+
sub_control = match.group(3)
|
|
96
|
+
|
|
97
|
+
if sub_control.isdigit():
|
|
98
|
+
transformed_control = f"{control_name}-{control_number}.{sub_control}"
|
|
99
|
+
else:
|
|
100
|
+
transformed_control = f"{control_name}-{control_number}"
|
|
101
|
+
|
|
102
|
+
return transformed_control
|
|
103
|
+
return control.lower()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def new_leveraged_auth(
|
|
107
|
+
ssp: SecurityPlan, user_id: str, instructions_data: dict, version: Literal["rev4", "rev5"]
|
|
108
|
+
) -> int:
|
|
109
|
+
"""
|
|
110
|
+
Function to create a new Leveraged Authorization in RegScale.
|
|
111
|
+
|
|
112
|
+
:param SecurityPlan ssp: RegScale SSP Object
|
|
113
|
+
:param str user_id: RegScale user ID
|
|
114
|
+
:param dict instructions_data: Data parsed from Instructions worksheet in the FedRAMP CIS CRM workbook
|
|
115
|
+
:param Literal["rev4", "rev5"] version: FedRAMP revision version
|
|
116
|
+
:return: Newly created Leveraged Authorization ID in RegScale
|
|
117
|
+
:rtype: int
|
|
118
|
+
"""
|
|
119
|
+
leveraged_auth = LeveragedAuthorization(
|
|
120
|
+
title=instructions_data[CSP],
|
|
121
|
+
servicesUsed=instructions_data[CSP],
|
|
122
|
+
fedrampId=(instructions_data["System Identifier"] if version == "rev5" else instructions_data[SYSTEM_NAME]),
|
|
123
|
+
authorizationType="FedRAMP Ready",
|
|
124
|
+
impactLevel=instructions_data[IMPACT_LEVEL],
|
|
125
|
+
dateAuthorized="",
|
|
126
|
+
natureOfAgreement="Other",
|
|
127
|
+
dataTypes="Other",
|
|
128
|
+
authorizedUserTypes="Other",
|
|
129
|
+
authenticationType="Other",
|
|
130
|
+
createdById=user_id,
|
|
131
|
+
securityPlanId=ssp.id,
|
|
132
|
+
ownerId=user_id,
|
|
133
|
+
lastUpdatedById=user_id,
|
|
134
|
+
description="Imported from FedRAMP CIS CRM Workbook on " + get_current_datetime("%m/%d/%Y %H:%M:%S"),
|
|
135
|
+
)
|
|
136
|
+
new_leveraged_auth_id = leveraged_auth.create()
|
|
137
|
+
return new_leveraged_auth_id.id
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def gen_key(control_id: str):
|
|
141
|
+
"""
|
|
142
|
+
Function to generate a key for the control ID
|
|
143
|
+
|
|
144
|
+
:param str control_id: The control ID to generate a key for
|
|
145
|
+
:return: The generated key
|
|
146
|
+
:rtype: str
|
|
147
|
+
"""
|
|
148
|
+
# Match pattern: captures everything up to either:
|
|
149
|
+
# 1. The last (number) if it exists
|
|
150
|
+
# 2. The main control number if no enhancement exists
|
|
151
|
+
# And excludes any trailing (letter)
|
|
152
|
+
pattern = r"^((?:\w+-\d+(?:\(\d+\))?))(?:\([a-zA-Z]\))?$"
|
|
153
|
+
|
|
154
|
+
match = re.match(pattern, control_id)
|
|
155
|
+
if match:
|
|
156
|
+
return match.group(1)
|
|
157
|
+
return control_id
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def map_implementation_status(control_id: str, cis_data: dict) -> str:
|
|
161
|
+
"""
|
|
162
|
+
Function to map the selected implementation status on the CIS worksheet to a RegScale status
|
|
163
|
+
|
|
164
|
+
:param str control_id: The control ID from RegScale
|
|
165
|
+
:param dict cis_data: Data from the CIS worksheet to map the status from
|
|
166
|
+
:return: RegScale control implementation status
|
|
167
|
+
:rtype: str
|
|
168
|
+
"""
|
|
169
|
+
|
|
170
|
+
# Extract matching records
|
|
171
|
+
cis_records = [
|
|
172
|
+
value
|
|
173
|
+
for value in cis_data.values()
|
|
174
|
+
if gen_key(value.get("regscale_control_id", "")).lower() == control_id.lower()
|
|
175
|
+
]
|
|
176
|
+
|
|
177
|
+
status_ret = ControlImplementationStatus.NotImplemented
|
|
178
|
+
|
|
179
|
+
logger.debug("Found %d CIS records for control %s", len(cis_records), control_id)
|
|
180
|
+
|
|
181
|
+
if not cis_records:
|
|
182
|
+
logger.warning(f"No CIS records found for control {control_id}")
|
|
183
|
+
return status_ret
|
|
184
|
+
|
|
185
|
+
# Count implementation statuses
|
|
186
|
+
status_counts = Counter(record.get("implementation_status", "") for record in cis_records)
|
|
187
|
+
logger.debug("Status distribution for %s: %s", control_id, dict(status_counts))
|
|
188
|
+
|
|
189
|
+
# Early returns for simple cases
|
|
190
|
+
if len(status_counts) == 1:
|
|
191
|
+
status = next(iter(status_counts))
|
|
192
|
+
return STATUS_MAPPING.get(status, ControlImplementationStatus.NotImplemented)
|
|
193
|
+
|
|
194
|
+
# Priority-based status determination
|
|
195
|
+
if any(status in ["N/A", "Alternative Implementation"] for status in status_counts):
|
|
196
|
+
status_ret = ControlImplementationStatus.NA
|
|
197
|
+
|
|
198
|
+
implemented_count = status_counts.get("Implemented", 0)
|
|
199
|
+
total_count = sum(status_counts.values())
|
|
200
|
+
|
|
201
|
+
if implemented_count == total_count:
|
|
202
|
+
status_ret = ControlImplementationStatus.FullyImplemented
|
|
203
|
+
elif implemented_count > 0 or any(status == "Partially Implemented" for status in status_counts):
|
|
204
|
+
status_ret = ControlImplementationStatus.PartiallyImplemented
|
|
205
|
+
elif any(status == "Planned" for status in status_counts):
|
|
206
|
+
status_ret = ControlImplementationStatus.Planned
|
|
207
|
+
|
|
208
|
+
return status_ret
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def map_origination(control_id: str, cis_data: dict) -> dict:
|
|
212
|
+
"""
|
|
213
|
+
Function to map the responsibility for a control implementation from the CRM worksheet
|
|
214
|
+
|
|
215
|
+
:param str control_id: RegScale control ID
|
|
216
|
+
:param dict cis_data: Data from the CRM worksheet
|
|
217
|
+
:return: The responsibility information in regscale format
|
|
218
|
+
:rtype: dict
|
|
219
|
+
"""
|
|
220
|
+
origination_bools = {
|
|
221
|
+
"bInherited": False,
|
|
222
|
+
"bServiceProviderCorporate": False,
|
|
223
|
+
"bServiceProviderSystemSpecific": False,
|
|
224
|
+
"bServiceProviderHybrid": False,
|
|
225
|
+
"bConfiguredByCustomer": False,
|
|
226
|
+
"bProvidedByCustomer": False,
|
|
227
|
+
"bShared": False,
|
|
228
|
+
"record_text": "",
|
|
229
|
+
}
|
|
230
|
+
cis_records = [
|
|
231
|
+
value for _, value in cis_data.items() if gen_key(value["regscale_control_id"]).lower() == control_id.lower()
|
|
232
|
+
]
|
|
233
|
+
for record in cis_records:
|
|
234
|
+
# Create the implementation objective, and save.
|
|
235
|
+
control_origination = record.get("control_origination", "")
|
|
236
|
+
if SERVICE_PROVIDER_CORPORATE in control_origination:
|
|
237
|
+
# responsibility = "Provider"
|
|
238
|
+
origination_bools["bServiceProviderCorporate"] = True
|
|
239
|
+
if SERVICE_PROVIDER_SYSTEM_SPECIFIC in control_origination:
|
|
240
|
+
# responsibility = "Provider (System Specific)"
|
|
241
|
+
origination_bools["bServiceProviderSystemSpecific"] = True
|
|
242
|
+
if SERVICE_PROVIDER_HYBRID in control_origination:
|
|
243
|
+
# responsibility = "Hybrid"
|
|
244
|
+
origination_bools["bServiceProviderHybrid"] = True
|
|
245
|
+
if CUSTOMER_PROVIDED in control_origination:
|
|
246
|
+
# responsibility = "Customer"
|
|
247
|
+
origination_bools["bProvidedByCustomer"] = True
|
|
248
|
+
if CONFIGURED_BY_CUSTOMER in control_origination:
|
|
249
|
+
# responsibility = "Customer Configured"
|
|
250
|
+
origination_bools["bConfiguredByCustomer"] = True
|
|
251
|
+
if "Shared" in control_origination:
|
|
252
|
+
# responsibility = "Shared"
|
|
253
|
+
origination_bools["bShared"] = True
|
|
254
|
+
if "Inherited" in control_origination:
|
|
255
|
+
# responsibility = "Inherited"
|
|
256
|
+
origination_bools["bInherited"] = True
|
|
257
|
+
origination_bools["record_text"] += control_origination
|
|
258
|
+
return origination_bools
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def clean_customer_responsibility(value: str):
|
|
262
|
+
"""
|
|
263
|
+
Function to clean the customer responsibility value
|
|
264
|
+
|
|
265
|
+
:param str value: The value to clean
|
|
266
|
+
:return: The cleaned value
|
|
267
|
+
:rtype: str
|
|
268
|
+
"""
|
|
269
|
+
if not value:
|
|
270
|
+
return ""
|
|
271
|
+
try:
|
|
272
|
+
return "" if math.isnan(float(value)) else str(value)
|
|
273
|
+
except (ValueError, TypeError):
|
|
274
|
+
return str(value)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def update_imp_objective(
|
|
278
|
+
leverage_auth_id: int,
|
|
279
|
+
existing_imp_obj: List[ImplementationObjective],
|
|
280
|
+
imp: ControlImplementation,
|
|
281
|
+
objectives: List[ControlObjective],
|
|
282
|
+
record: dict,
|
|
283
|
+
) -> Optional[ImplementationObjective]:
|
|
284
|
+
"""
|
|
285
|
+
Update the control objective with the given record data.
|
|
286
|
+
|
|
287
|
+
:param int leverage_auth_id: The leveraged authorization ID
|
|
288
|
+
:param List[ImplementationObjective] existing_imp_obj: The existing implementation objective
|
|
289
|
+
:param ControlImplementation imp: The control implementation to update
|
|
290
|
+
:param List[ControlObjective] objectives: The control objective to update
|
|
291
|
+
:param dict record: The CIS/CRM record data to update the objective with
|
|
292
|
+
:rtype: Optional[ImplementationObjective]
|
|
293
|
+
:return: The updated or created implementation objective
|
|
294
|
+
"""
|
|
295
|
+
status_map = {
|
|
296
|
+
"Implemented": ControlImplementationStatus.Implemented.value,
|
|
297
|
+
"Planned": ControlImplementationStatus.Implemented.Planned.value,
|
|
298
|
+
PARTIALLY_IMPLEMENTED: PARTIALLY_IMPLEMENTED,
|
|
299
|
+
"N/A": ControlImplementationStatus.NA.value,
|
|
300
|
+
NOT_IMPLEMENTED: NOT_IMPLEMENTED,
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
responsibility_map = {
|
|
304
|
+
"Provider": SERVICE_PROVIDER_CORPORATE,
|
|
305
|
+
PROVIDER_SYSTEM_SPECIFIC: SERVICE_PROVIDER_SYSTEM_SPECIFIC,
|
|
306
|
+
"Customer": "Provided by Customer (Customer System Specific)",
|
|
307
|
+
"Hybrid": "Service Provider Hybrid (Corporate and System Specific)",
|
|
308
|
+
CUSTOMER_CONFIGURED: "Configured by Customer (Customer System Specific)",
|
|
309
|
+
"Shared": "Shared (Service Provider and Customer Responsibility)",
|
|
310
|
+
"Inherited": "Inherited from pre-existing FedRAMP Authorization",
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
cis_record = record.get("cis", {})
|
|
314
|
+
crm_record = record.get("crm", {})
|
|
315
|
+
responsibility = RESPONSIBILITY_MAP.get(
|
|
316
|
+
cis_record.get("control_origination", ""), ControlImplementationStatus.NA.value
|
|
317
|
+
)
|
|
318
|
+
customer_responsibility = clean_customer_responsibility(
|
|
319
|
+
crm_record.get("specific_inheritance_and_customer_agency_csp_responsibilities")
|
|
320
|
+
)
|
|
321
|
+
ret_objective = None
|
|
322
|
+
existing_pairs = {(obj.objectiveId, obj.implementationId) for obj in existing_imp_obj}
|
|
323
|
+
for objective in objectives:
|
|
324
|
+
current_pair = (objective.id, imp.id)
|
|
325
|
+
if current_pair not in existing_pairs:
|
|
326
|
+
imp_obj = ImplementationObjective(
|
|
327
|
+
id=0,
|
|
328
|
+
uuid="",
|
|
329
|
+
inherited=crm_record.get("can_be_inherited_from_csp") == "Yes",
|
|
330
|
+
implementationId=imp.id,
|
|
331
|
+
status=status_map.get(cis_record.get("implementation_status", NOT_IMPLEMENTED), NOT_IMPLEMENTED),
|
|
332
|
+
objectiveId=objective.id,
|
|
333
|
+
notes=objective.name,
|
|
334
|
+
securityControlId=objective.securityControlId,
|
|
335
|
+
responsibility=responsibility_map.get(responsibility, responsibility),
|
|
336
|
+
cloudResponsibility=customer_responsibility,
|
|
337
|
+
customerResponsibility=customer_responsibility,
|
|
338
|
+
authorizationId=leverage_auth_id,
|
|
339
|
+
)
|
|
340
|
+
ret_objective = imp_obj.create()
|
|
341
|
+
else:
|
|
342
|
+
# NOTE: Don't overwrite the responsibility text and only append.
|
|
343
|
+
ex_obj = next((obj for obj in existing_imp_obj if obj.objectiveId == objective.id), None)
|
|
344
|
+
if ex_obj:
|
|
345
|
+
ex_obj.status = status_map.get(
|
|
346
|
+
cis_record.get("implementation_status", NOT_IMPLEMENTED), NOT_IMPLEMENTED
|
|
347
|
+
)
|
|
348
|
+
try:
|
|
349
|
+
seperator = " \n---------------\n "
|
|
350
|
+
if ex_obj.responsibility:
|
|
351
|
+
ex_obj.responsibility = (
|
|
352
|
+
seperator.join([ex_obj.responsibility, responsibility])
|
|
353
|
+
if ex_obj.responsibility != responsibility
|
|
354
|
+
else ex_obj.responsibility
|
|
355
|
+
)
|
|
356
|
+
if ex_obj.cloudResponsibility:
|
|
357
|
+
ex_obj.cloudResponsibility = (
|
|
358
|
+
seperator.join([ex_obj.cloudResponsibility, responsibility])
|
|
359
|
+
if ex_obj.cloudResponsibility != responsibility
|
|
360
|
+
else ex_obj.cloudResponsibility
|
|
361
|
+
)
|
|
362
|
+
if ex_obj.customerResponsibility:
|
|
363
|
+
ex_obj.customerResponsibility = (
|
|
364
|
+
seperator.join([ex_obj.customerResponsibility, responsibility])
|
|
365
|
+
if ex_obj.cloudResponsibility != responsibility
|
|
366
|
+
else ex_obj.customerResponsibility
|
|
367
|
+
)
|
|
368
|
+
except TypeError:
|
|
369
|
+
logger.warning(f"Failed to update responsibility on Implementation Objective #{ex_obj.id}")
|
|
370
|
+
ret_objective = ex_obj.save()
|
|
371
|
+
|
|
372
|
+
return ret_objective
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def parse_control_details(
|
|
376
|
+
version: Literal["rev4", "rev5"], control_imp: ControlImplementation, control: SecurityControl, cis_data: dict
|
|
377
|
+
) -> ControlImplementation:
|
|
378
|
+
"""
|
|
379
|
+
Function to parse control details from RegScale and CIS data and returns an updated ControlImplementation object
|
|
380
|
+
|
|
381
|
+
:param Literal["rev4", "rev5"] version: The version of the workbook
|
|
382
|
+
:param ControlImplementation control_imp: RegScale ControlImplementation object to update
|
|
383
|
+
:param SecurityControl control: RegScale control
|
|
384
|
+
:param dict cis_data: Data from the CIS worksheet
|
|
385
|
+
:return: Updated ControlImplementation object
|
|
386
|
+
:rtype: ControlImplementation
|
|
387
|
+
"""
|
|
388
|
+
control_id = control.controlId if version == "rev5" else control.sortId
|
|
389
|
+
status = map_implementation_status(control_id=control_id, cis_data=cis_data)
|
|
390
|
+
origination_bool = map_origination(control_id=control_id, cis_data=cis_data)
|
|
391
|
+
control_imp.status = status
|
|
392
|
+
if status == ControlImplementationStatus.Planned:
|
|
393
|
+
control_imp.plannedImplementationDate = get_current_datetime("%Y-%m-%d")
|
|
394
|
+
control_imp.stepsToImplement = "To be updated"
|
|
395
|
+
control_imp.controlSource = "Baseline" if not origination_bool["bInherited"] else "Inherited"
|
|
396
|
+
control_imp.exclusionJustification = (
|
|
397
|
+
"Imported from FedRAMP CIS CRM Workbook" if status == ControlImplementationStatus.NA else None
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
control_imp.bInherited = origination_bool["bInherited"]
|
|
401
|
+
control_imp.inheritable = origination_bool["bInherited"]
|
|
402
|
+
control_imp.bServiceProviderCorporate = origination_bool["bServiceProviderCorporate"]
|
|
403
|
+
control_imp.bServiceProviderSystemSpecific = origination_bool["bServiceProviderSystemSpecific"]
|
|
404
|
+
control_imp.bServiceProviderHybrid = origination_bool["bServiceProviderHybrid"]
|
|
405
|
+
control_imp.bConfiguredByCustomer = origination_bool["bConfiguredByCustomer"]
|
|
406
|
+
control_imp.bProvidedByCustomer = origination_bool["bProvidedByCustomer"]
|
|
407
|
+
# NOTE Dale was concerned with overwriting the responsibility text, so we will only update if empty
|
|
408
|
+
if not control_imp.responsibility:
|
|
409
|
+
control_imp.responsibility = get_responsibility(origination_bool)
|
|
410
|
+
if updated_control := control_imp.save():
|
|
411
|
+
logger.debug("Control Implementation #%s updated successfully", control_imp.id)
|
|
412
|
+
return updated_control
|
|
413
|
+
logger.error("Failed to update Control Implementation \n" + json.dumps(control_imp.model_dump()))
|
|
414
|
+
return control_imp
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def get_responsibility(origination_bool: dict) -> str:
|
|
418
|
+
"""
|
|
419
|
+
Function to map the responsibility based on origination booleans.
|
|
420
|
+
|
|
421
|
+
:param dict origination_bool: Dictionary containing origination booleans
|
|
422
|
+
:return: Responsibility string
|
|
423
|
+
:rtype: str
|
|
424
|
+
"""
|
|
425
|
+
responsibility = ControlImplementationStatus.NA.value
|
|
426
|
+
|
|
427
|
+
if origination_bool["bServiceProviderCorporate"]:
|
|
428
|
+
responsibility = SERVICE_PROVIDER_CORPORATE
|
|
429
|
+
if origination_bool["bServiceProviderSystemSpecific"]:
|
|
430
|
+
responsibility = SERVICE_PROVIDER_SYSTEM_SPECIFIC
|
|
431
|
+
if origination_bool["bServiceProviderHybrid"]:
|
|
432
|
+
responsibility = "Service Provider Hybrid"
|
|
433
|
+
if origination_bool["bProvidedByCustomer"]:
|
|
434
|
+
responsibility = "Provided by Customer"
|
|
435
|
+
if origination_bool["bConfiguredByCustomer"]:
|
|
436
|
+
responsibility = "Configured by Customer (Customer System Specific)"
|
|
437
|
+
if origination_bool["bInherited"]:
|
|
438
|
+
responsibility = "Inherited from pre-existing FedRAMP Authorization"
|
|
439
|
+
if origination_bool["bShared"]:
|
|
440
|
+
responsibility = "Shared (Service Provider and Customer Responsibility)"
|
|
441
|
+
|
|
442
|
+
return responsibility
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def fetch_and_update_imps(
|
|
446
|
+
control: dict, api: Api, cis_data: dict, version: Literal["rev4", "rev5"]
|
|
447
|
+
) -> Optional[ControlImplementation]:
|
|
448
|
+
"""
|
|
449
|
+
Function to fetch implementation objectives from RegScale via API
|
|
450
|
+
|
|
451
|
+
:param dict control: RegScale control as a dictionary
|
|
452
|
+
:param Api api: RegScale API object
|
|
453
|
+
:param dict cis_data: Data from the CIS worksheet
|
|
454
|
+
:param Literal["rev4", "rev5"] version: The version of the workbook
|
|
455
|
+
:return: An updated control implementation if found
|
|
456
|
+
:rtype: Optional[ControlImplementation]
|
|
457
|
+
"""
|
|
458
|
+
# get the control and control implementation objects
|
|
459
|
+
regscale_control = SecurityControl.get_object(control["scId"])
|
|
460
|
+
regscale_control_imp = ControlImplementation.get_object(control["id"])
|
|
461
|
+
|
|
462
|
+
if not regscale_control or not regscale_control_imp:
|
|
463
|
+
api.logger.error("Failed to fetch control or control implementation")
|
|
464
|
+
return regscale_control_imp
|
|
465
|
+
|
|
466
|
+
updated_control = parse_control_details(
|
|
467
|
+
version=version, control_imp=regscale_control_imp, control=regscale_control, cis_data=cis_data
|
|
468
|
+
)
|
|
469
|
+
return updated_control
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def get_all_imps(api: Api, ssp_id: int, cis_data: dict, version: Literal["rev4", "rev5"]) -> list:
|
|
473
|
+
"""
|
|
474
|
+
Function to retrieve control implementations and their objectives from RegScale
|
|
475
|
+
|
|
476
|
+
:param Api api: The RegScale API object
|
|
477
|
+
:param int ssp_id: The SSP ID
|
|
478
|
+
:param dict cis_data: The data from the CIS worksheet
|
|
479
|
+
:param Literal["rev4", "rev5"] version: The version of the workbook
|
|
480
|
+
:return: List of updated control implementations
|
|
481
|
+
:rtype: list
|
|
482
|
+
"""
|
|
483
|
+
from requests import RequestException
|
|
484
|
+
|
|
485
|
+
updated_controls = []
|
|
486
|
+
url = urljoin(api.config["domain"], f"/api/controlImplementation/getSCListByPlan/{ssp_id}")
|
|
487
|
+
response = api.get(url)
|
|
488
|
+
|
|
489
|
+
if response.status_code == 404:
|
|
490
|
+
api.logger.warning(f"SSP with ID {ssp_id} has no controls.")
|
|
491
|
+
return updated_controls
|
|
492
|
+
|
|
493
|
+
# Check if the response is successful
|
|
494
|
+
if response.status_code == 200:
|
|
495
|
+
ssp_controls = response.json()
|
|
496
|
+
# Get Control Implementations For SSP
|
|
497
|
+
fetching_imps = progress.add_task(
|
|
498
|
+
f"[magenta]Fetching & updating {len(ssp_controls)} implementation(s)...", total=len(ssp_controls)
|
|
499
|
+
)
|
|
500
|
+
with ThreadPoolExecutor(max_workers=50) as executor:
|
|
501
|
+
futures = [
|
|
502
|
+
executor.submit(fetch_and_update_imps, control, api, cis_data, version) for control in ssp_controls
|
|
503
|
+
]
|
|
504
|
+
for future in as_completed(futures):
|
|
505
|
+
progress.update(fetching_imps, advance=1)
|
|
506
|
+
try:
|
|
507
|
+
controls = future.result()
|
|
508
|
+
updated_controls.append(controls)
|
|
509
|
+
except (RequestException, TimeoutError) as ex:
|
|
510
|
+
api.logger.error(f"Error fetching control implementations: {ex}")
|
|
511
|
+
else:
|
|
512
|
+
api.logger.error(f"Failed to fetch controls: {response.status_code}: {response.reason}")
|
|
513
|
+
|
|
514
|
+
return updated_controls
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
def get_all_control_objectives(imps: List[ControlImplementation]) -> List[ControlObjective]:
|
|
518
|
+
"""
|
|
519
|
+
Get All Control Objectives from GraphQL
|
|
520
|
+
|
|
521
|
+
:param List[ControlImplementation] imps: The Implementations
|
|
522
|
+
:return: List of ControlObjective
|
|
523
|
+
:rtype: List[ControlObjective]
|
|
524
|
+
"""
|
|
525
|
+
api = Api()
|
|
526
|
+
res = []
|
|
527
|
+
# list of int to string
|
|
528
|
+
if imps:
|
|
529
|
+
query = GraphQLQuery()
|
|
530
|
+
query.start_query()
|
|
531
|
+
query.add_query(
|
|
532
|
+
entity="controlObjectives",
|
|
533
|
+
items=["id", "description", "otherId", "name", "securityControlId"],
|
|
534
|
+
where={"securityControlId": {"in": [c.controlID for c in imps]}},
|
|
535
|
+
)
|
|
536
|
+
query.end_query()
|
|
537
|
+
dat = api.graph(query=query.build())
|
|
538
|
+
res = [ControlObjective(**d) for d in dat.get("controlObjectives", {}).get("items", [])]
|
|
539
|
+
return res
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
def update_all_objectives(
|
|
543
|
+
leveraged_auth_id: int,
|
|
544
|
+
cis_data: Dict[str, Dict[str, str]],
|
|
545
|
+
crm_data: Dict[str, Dict[str, str]],
|
|
546
|
+
control_implementations: List[ControlImplementation],
|
|
547
|
+
version: Literal["rev4", "rev5"],
|
|
548
|
+
) -> set:
|
|
549
|
+
"""
|
|
550
|
+
Updates all objectives for the given control implementations based on CIS worksheet data.
|
|
551
|
+
Uses parallel processing and displays progress bars.
|
|
552
|
+
|
|
553
|
+
:param int leveraged_auth_id: The leveraged authorization ID
|
|
554
|
+
:param Dict[str, Dict[str, str]] cis_data: The CIS data to update from
|
|
555
|
+
:param Dict[str, Dict[str, str]] crm_data: The CRM data to update from
|
|
556
|
+
:param List[ControlImplementation] control_implementations: The control implementations to update
|
|
557
|
+
:param Literal["rev4", "rev5"] version: The version of the workbook
|
|
558
|
+
:return: A set of errors, if any
|
|
559
|
+
:rtype: set
|
|
560
|
+
"""
|
|
561
|
+
all_control_objectives = get_all_control_objectives(imps=control_implementations)
|
|
562
|
+
error_set = set()
|
|
563
|
+
task = progress.add_task("[cyan]Processing control objectives...", total=len(control_implementations))
|
|
564
|
+
# Create a combined dataset for easier access
|
|
565
|
+
combined_data = {key: {"cis": cis_data[key], "crm": crm_data.get(key, {})} for key in cis_data}
|
|
566
|
+
|
|
567
|
+
# Process implementations in parallel
|
|
568
|
+
with ThreadPoolExecutor(max_workers=50) as executor:
|
|
569
|
+
# Submit all tasks
|
|
570
|
+
future_to_control = {
|
|
571
|
+
executor.submit(
|
|
572
|
+
process_implementation, leveraged_auth_id, imp, combined_data, version, all_control_objectives
|
|
573
|
+
): imp
|
|
574
|
+
for imp in control_implementations
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
# Process results as they complete
|
|
578
|
+
for future in as_completed(future_to_control):
|
|
579
|
+
result = future.result()
|
|
580
|
+
if isinstance(result[0], list):
|
|
581
|
+
error_lst = result[0]
|
|
582
|
+
for inf in error_lst:
|
|
583
|
+
error_set.add(inf)
|
|
584
|
+
progress.update(task, advance=1)
|
|
585
|
+
|
|
586
|
+
return error_set
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
def report(error_set: set):
|
|
590
|
+
"""
|
|
591
|
+
Function to report errors to the user
|
|
592
|
+
|
|
593
|
+
:param set error_set: Set of errors to report
|
|
594
|
+
:rtype: None
|
|
595
|
+
"""
|
|
596
|
+
from rich.console import Console
|
|
597
|
+
from rich.table import Table
|
|
598
|
+
|
|
599
|
+
console = Console()
|
|
600
|
+
|
|
601
|
+
if error_set:
|
|
602
|
+
table = Table(title="Unmapped Control Objectives")
|
|
603
|
+
|
|
604
|
+
table.add_column(justify="left", style="red", no_wrap=True)
|
|
605
|
+
|
|
606
|
+
for error in sorted(error_set):
|
|
607
|
+
table.add_row(error)
|
|
608
|
+
|
|
609
|
+
console.print(table)
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
def process_implementation(
|
|
613
|
+
leveraged_auth_id: int,
|
|
614
|
+
implementation: ControlImplementation,
|
|
615
|
+
sheet_data: dict,
|
|
616
|
+
version: Literal["rev4", "rev5"],
|
|
617
|
+
all_objectives: List[ControlObjective],
|
|
618
|
+
) -> Tuple[List[str], List[ImplementationObjective]]:
|
|
619
|
+
"""
|
|
620
|
+
Processes a single implementation and its associated records.
|
|
621
|
+
|
|
622
|
+
:param int leveraged_auth_id: The leveraged authorization ID
|
|
623
|
+
:param ControlImplementation implementation: The control implementation to process
|
|
624
|
+
:param dict sheet_data: The CIS/CRM data to process
|
|
625
|
+
:param Literal["rev4", "rev5"] version: The version of the workbook
|
|
626
|
+
:param List[ControlObjective] all_objectives: all the control objectives
|
|
627
|
+
:rtype Tuple[List[str], List[ImplementationObjective]]
|
|
628
|
+
:returns A list of updated implementation objectives
|
|
629
|
+
"""
|
|
630
|
+
|
|
631
|
+
errors = []
|
|
632
|
+
processed_objectives = []
|
|
633
|
+
|
|
634
|
+
existing_objectives, filtered_records = gen_filtered_records(implementation, sheet_data, version)
|
|
635
|
+
result = None
|
|
636
|
+
for record in filtered_records:
|
|
637
|
+
res = process_single_record(
|
|
638
|
+
leveraged_auth_id=leveraged_auth_id,
|
|
639
|
+
implementation=implementation,
|
|
640
|
+
record=record,
|
|
641
|
+
control_objectives=all_objectives,
|
|
642
|
+
existing_objectives=existing_objectives,
|
|
643
|
+
version=version,
|
|
644
|
+
)
|
|
645
|
+
if isinstance(res, tuple):
|
|
646
|
+
method_errors, result = res
|
|
647
|
+
errors.extend(method_errors)
|
|
648
|
+
if result:
|
|
649
|
+
processed_objectives.append(result)
|
|
650
|
+
return errors, processed_objectives
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
def gen_filtered_records(
|
|
654
|
+
implementation: ControlImplementation, sheet_data: dict, version: Literal["rev4", "rev5"]
|
|
655
|
+
) -> Tuple[List[ImplementationObjective], List[Dict[str, str]]]:
|
|
656
|
+
"""
|
|
657
|
+
Generates filtered records for a given implementation.
|
|
658
|
+
|
|
659
|
+
:param ControlImplementation implementation: The control implementation to filter records for
|
|
660
|
+
:param dict sheet_data: The CIS/CRM data to filter
|
|
661
|
+
:param Literal["rev4", "rev5"] version: The version of the workbook
|
|
662
|
+
:returns A tuple of existing objectives, and filtered records
|
|
663
|
+
:rtype Tuple[List[ImplementationObjective], List[Dict[str, str]]]
|
|
664
|
+
"""
|
|
665
|
+
security_control = SecurityControl.get_object(implementation.controlID)
|
|
666
|
+
existing_objectives = ImplementationObjective.get_by_control(implementation.id)
|
|
667
|
+
if version == "rev5":
|
|
668
|
+
filtered_records = filter(
|
|
669
|
+
lambda r: extract_control_name(r["cis"]["regscale_control_id"]).lower()
|
|
670
|
+
== security_control.controlId.lower(),
|
|
671
|
+
sheet_data.values(),
|
|
672
|
+
)
|
|
673
|
+
else:
|
|
674
|
+
try:
|
|
675
|
+
control_label = next(
|
|
676
|
+
dat for dat in part_mapper_rev4.data if dat.get("Oscal Control ID") == security_control.controlId
|
|
677
|
+
).get("CONTROLLABEL")
|
|
678
|
+
except StopIteration:
|
|
679
|
+
control_label = None
|
|
680
|
+
if control_label:
|
|
681
|
+
filtered_records = [r for r in sheet_data.values() if r["cis"]["regscale_control_id"] == control_label]
|
|
682
|
+
else:
|
|
683
|
+
filtered_records = []
|
|
684
|
+
|
|
685
|
+
return existing_objectives, filtered_records
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
def get_matching_cis_records(control_id: str, cis_data: dict) -> List[Dict[str, str]]:
|
|
689
|
+
"""
|
|
690
|
+
Finds matching CIS records for a given control ID.
|
|
691
|
+
|
|
692
|
+
:param str control_id: The control ID to match
|
|
693
|
+
:param dict cis_data: The CIS data to search
|
|
694
|
+
:rtype List[Dict[str, str]]
|
|
695
|
+
:returns A list of matching CIS records
|
|
696
|
+
"""
|
|
697
|
+
return [value for value in cis_data.values() if value["regscale_control_id"].lower() == control_id.lower()]
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
def process_single_record(**kwargs) -> Tuple[List[str], Optional[ImplementationObjective]]:
|
|
701
|
+
"""
|
|
702
|
+
Processes a single CIS record and returns updated objective if successful.
|
|
703
|
+
|
|
704
|
+
:rtype Tuple[List[str], Optional[ImplementationObjective]]
|
|
705
|
+
:returns A list of errors and the Implementation Objective if successful, otherwise None
|
|
706
|
+
"""
|
|
707
|
+
errors = []
|
|
708
|
+
version = kwargs.get("version")
|
|
709
|
+
leveraged_auth_id: int = kwargs.get("leveraged_auth_id")
|
|
710
|
+
implementation: ControlImplementation = kwargs.get("implementation")
|
|
711
|
+
record: dict = kwargs.get("record")
|
|
712
|
+
control_objectives: List[ControlObjective] = kwargs.get("control_objectives")
|
|
713
|
+
existing_objectives: List[ImplementationObjective] = kwargs.get("existing_objectives")
|
|
714
|
+
mapped_objectives: List[ControlObjective] = []
|
|
715
|
+
result = None
|
|
716
|
+
parts = []
|
|
717
|
+
key = record["cis"]["control_id"]
|
|
718
|
+
if version == "rev5":
|
|
719
|
+
source = part_mapper_rev5.find_by_source(key)
|
|
720
|
+
else:
|
|
721
|
+
source = part_mapper_rev4.find_by_source(key)
|
|
722
|
+
if parts := part_mapper_rev4.find_sub_parts(key):
|
|
723
|
+
for part in parts:
|
|
724
|
+
try:
|
|
725
|
+
mapped_objectives.append(next(obj for obj in control_objectives if obj.name == part))
|
|
726
|
+
except StopIteration:
|
|
727
|
+
errors.append(f"Unable to find part {part} for control {key}")
|
|
728
|
+
if not source and not parts:
|
|
729
|
+
errors.append(f"Unable to find source and part for control {key}")
|
|
730
|
+
|
|
731
|
+
if source and not parts:
|
|
732
|
+
try:
|
|
733
|
+
objective = next(
|
|
734
|
+
obj
|
|
735
|
+
for obj in control_objectives
|
|
736
|
+
if obj.otherId == source and version == "rev5" or obj.name == source and version == "rev4"
|
|
737
|
+
)
|
|
738
|
+
mapped_objectives.append(objective)
|
|
739
|
+
except StopIteration:
|
|
740
|
+
logger.debug(f"Missing Source: {source}")
|
|
741
|
+
errors.append(f"Unable to find objective for control {key} ({source})")
|
|
742
|
+
|
|
743
|
+
if mapped_objectives:
|
|
744
|
+
result = update_imp_objective(
|
|
745
|
+
leverage_auth_id=leveraged_auth_id,
|
|
746
|
+
existing_imp_obj=existing_objectives,
|
|
747
|
+
imp=implementation,
|
|
748
|
+
objectives=mapped_objectives,
|
|
749
|
+
record=record,
|
|
750
|
+
)
|
|
751
|
+
|
|
752
|
+
return errors, result
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
def parse_crm_worksheet(file_path: click.Path, crm_sheet_name: str, version: Literal["rev4", "rev5"]) -> dict:
|
|
756
|
+
"""
|
|
757
|
+
Function to format CRM content.
|
|
758
|
+
|
|
759
|
+
:param click.Path file_path: The file path to the FedRAMP CIS CRM workbook
|
|
760
|
+
:param str crm_sheet_name: The name of the CRM sheet to parse
|
|
761
|
+
:param Literal["rev4", "rev5"] version: The version of the workbook
|
|
762
|
+
:return: Formatted CRM content
|
|
763
|
+
:rtype: dict
|
|
764
|
+
"""
|
|
765
|
+
formatted_crm = {}
|
|
766
|
+
|
|
767
|
+
if not crm_sheet_name:
|
|
768
|
+
return formatted_crm
|
|
769
|
+
import pandas as pd # Optimize import performance
|
|
770
|
+
|
|
771
|
+
if version == "rev5":
|
|
772
|
+
skip_rows = 2
|
|
773
|
+
else:
|
|
774
|
+
skip_rows = 3
|
|
775
|
+
|
|
776
|
+
data = pd.read_excel(
|
|
777
|
+
str(file_path),
|
|
778
|
+
sheet_name=crm_sheet_name,
|
|
779
|
+
skiprows=skip_rows,
|
|
780
|
+
usecols=[
|
|
781
|
+
CONTROL_ID,
|
|
782
|
+
"Can Be Inherited from CSP",
|
|
783
|
+
"Specific Inheritance and Customer Agency/CSP Responsibilities",
|
|
784
|
+
],
|
|
785
|
+
)
|
|
786
|
+
|
|
787
|
+
# Filter rows where "Can Be Inherited from CSP" is not equal to "No"
|
|
788
|
+
exclude_no = data[data[CAN_BE_INHERITED_CSP] != "No"]
|
|
789
|
+
|
|
790
|
+
# Iterate through each row and add to the dictionary
|
|
791
|
+
for _, row in exclude_no.iterrows():
|
|
792
|
+
control_id = row[CONTROL_ID]
|
|
793
|
+
|
|
794
|
+
# Convert camel case to snake case, remove special characters, and convert to lowercase
|
|
795
|
+
clean_control_id = re.sub(r"\W+", "", control_id)
|
|
796
|
+
clean_control_id = re.sub("([a-z0-9])([A-Z])", r"\1_\2", clean_control_id).lower()
|
|
797
|
+
|
|
798
|
+
# Use clean_control_id as the key to avoid overwriting
|
|
799
|
+
formatted_crm[clean_control_id] = {
|
|
800
|
+
"control_id": clean_control_id,
|
|
801
|
+
"control_id_original": control_id,
|
|
802
|
+
"regscale_control_id": transform_control(control_id),
|
|
803
|
+
"can_be_inherited_from_csp": row[CAN_BE_INHERITED_CSP],
|
|
804
|
+
"specific_inheritance_and_customer_agency_csp_responsibilities": row[
|
|
805
|
+
"Specific Inheritance and Customer Agency/CSP Responsibilities"
|
|
806
|
+
],
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
return formatted_crm
|
|
810
|
+
|
|
811
|
+
|
|
812
|
+
def parse_cis_worksheet(file_path: click.Path, cis_sheet_name: str) -> dict:
|
|
813
|
+
"""
|
|
814
|
+
Function to parse and format the CIS worksheet content
|
|
815
|
+
|
|
816
|
+
:param click.Path file_path: The file path to the FedRAMP CIS CRM workbook
|
|
817
|
+
:param str cis_sheet_name: The name of the CIS sheet to parse
|
|
818
|
+
:return: Formatted CIS content
|
|
819
|
+
:rtype: dict
|
|
820
|
+
"""
|
|
821
|
+
import pandas as pd # Optimize import performance
|
|
822
|
+
|
|
823
|
+
# Parse the worksheet named 'CIS GovCloud U.S.+DoD (H)', skipping the initial rows
|
|
824
|
+
cis_df = pd.read_excel(file_path, sheet_name=cis_sheet_name, skiprows=2)
|
|
825
|
+
|
|
826
|
+
# Set the appropriate headers
|
|
827
|
+
cis_df.columns = cis_df.iloc[0]
|
|
828
|
+
|
|
829
|
+
# Drop any fully empty rows
|
|
830
|
+
cis_df.dropna(how="all", inplace=True)
|
|
831
|
+
|
|
832
|
+
# Reset the index
|
|
833
|
+
cis_df.reset_index(drop=True, inplace=True)
|
|
834
|
+
|
|
835
|
+
# Rename columns to standardize names
|
|
836
|
+
cis_df.columns = [
|
|
837
|
+
CONTROL_ID,
|
|
838
|
+
"Implemented",
|
|
839
|
+
ControlImplementationStatus.PartiallyImplemented,
|
|
840
|
+
"Planned",
|
|
841
|
+
ALT_IMPLEMENTATION,
|
|
842
|
+
ControlImplementationStatus.NA,
|
|
843
|
+
SERVICE_PROVIDER_CORPORATE,
|
|
844
|
+
SERVICE_PROVIDER_SYSTEM_SPECIFIC,
|
|
845
|
+
SERVICE_PROVIDER_HYBRID,
|
|
846
|
+
CONFIGURED_BY_CUSTOMER,
|
|
847
|
+
CUSTOMER_PROVIDED,
|
|
848
|
+
"Shared Responsibility",
|
|
849
|
+
"Inherited Authorization",
|
|
850
|
+
]
|
|
851
|
+
|
|
852
|
+
# Fill NaN values with an empty string for processing
|
|
853
|
+
cis_df = cis_df.fillna("")
|
|
854
|
+
|
|
855
|
+
# Function to extract the first non-empty implementation status
|
|
856
|
+
def _extract_status(data_row: pd.Series) -> str:
|
|
857
|
+
"""
|
|
858
|
+
Function to extract the first non-empty implementation status from the CIS worksheet
|
|
859
|
+
|
|
860
|
+
:param pd.Series data_row: The data row to extract the status from
|
|
861
|
+
:return: The implementation status
|
|
862
|
+
:rtype: str
|
|
863
|
+
"""
|
|
864
|
+
for col in [
|
|
865
|
+
"Implemented",
|
|
866
|
+
ControlImplementationStatus.PartiallyImplemented,
|
|
867
|
+
"Planned",
|
|
868
|
+
ALT_IMPLEMENTATION,
|
|
869
|
+
ControlImplementationStatus.NA,
|
|
870
|
+
]:
|
|
871
|
+
if data_row[col]:
|
|
872
|
+
return col
|
|
873
|
+
return ""
|
|
874
|
+
|
|
875
|
+
# Function to extract the first non-empty control origination
|
|
876
|
+
def _extract_origination(data_row: pd.Series) -> str:
|
|
877
|
+
"""
|
|
878
|
+
Function to extract the first non-empty control origination from the CIS worksheet
|
|
879
|
+
|
|
880
|
+
:param pd.Series data_row: The data row to extract the origination from
|
|
881
|
+
:return: The control origination
|
|
882
|
+
:rtype: str
|
|
883
|
+
"""
|
|
884
|
+
selected_origination = []
|
|
885
|
+
for col in [
|
|
886
|
+
SERVICE_PROVIDER_CORPORATE,
|
|
887
|
+
SERVICE_PROVIDER_SYSTEM_SPECIFIC,
|
|
888
|
+
SERVICE_PROVIDER_HYBRID,
|
|
889
|
+
CONFIGURED_BY_CUSTOMER,
|
|
890
|
+
CUSTOMER_PROVIDED,
|
|
891
|
+
"Shared Responsibility",
|
|
892
|
+
"Inherited Authorization",
|
|
893
|
+
]:
|
|
894
|
+
if data_row[col]:
|
|
895
|
+
selected_origination.append(col)
|
|
896
|
+
return ", ".join(selected_origination) if selected_origination else ""
|
|
897
|
+
|
|
898
|
+
def _process_row(row: pd.Series) -> dict:
|
|
899
|
+
"""
|
|
900
|
+
Function to process a row from the CIS worksheet
|
|
901
|
+
|
|
902
|
+
:param pd.Series row: The row to process
|
|
903
|
+
:return: The processed row
|
|
904
|
+
:rtype: dict
|
|
905
|
+
"""
|
|
906
|
+
return {
|
|
907
|
+
"control_id": row[CONTROL_ID],
|
|
908
|
+
"regscale_control_id": transform_control(row[CONTROL_ID]),
|
|
909
|
+
"implementation_status": _extract_status(row),
|
|
910
|
+
"control_origination": _extract_origination(row),
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
# use a threadexecutor to process the rows in parallel
|
|
914
|
+
with ThreadPoolExecutor() as executor:
|
|
915
|
+
results = list(executor.map(_process_row, [row for _, row in cis_df.iterrows()]))
|
|
916
|
+
|
|
917
|
+
# iterate the results and index by control_id
|
|
918
|
+
return {result["control_id"]: result for result in results}
|
|
919
|
+
|
|
920
|
+
|
|
921
|
+
def parse_instructions_worksheet(
|
|
922
|
+
file_path: click.Path, version: Literal["rev4", "rev5"], instructions_sheet_name: str = "Instructions"
|
|
923
|
+
) -> list[dict]:
|
|
924
|
+
"""
|
|
925
|
+
Function to parse the instructions sheet from the FedRAMP Rev5 CIS/CRM workbook
|
|
926
|
+
|
|
927
|
+
:param click.Path file_path: The file path to the FedRAMP CIS CRM workbook
|
|
928
|
+
:param Literal["rev4", "rev5"] version: The version of the FedRAMP CIS CRM workbook
|
|
929
|
+
:param str instructions_sheet_name: The name of the instructions sheet to parse, defaults to "Instructions"
|
|
930
|
+
:return: List of formatted instructions content as a dictionary
|
|
931
|
+
:rtype: list[dict]
|
|
932
|
+
"""
|
|
933
|
+
import pandas as pd # Optimize import performance
|
|
934
|
+
|
|
935
|
+
instructions_df = pd.read_excel(str(file_path), sheet_name=instructions_sheet_name, skiprows=2)
|
|
936
|
+
|
|
937
|
+
if version == "rev5":
|
|
938
|
+
# Set the appropriate headers
|
|
939
|
+
instructions_df.columns = instructions_df.iloc[0]
|
|
940
|
+
instructions_df = instructions_df[1:]
|
|
941
|
+
relevant_columns = [SYSTEM_NAME, CSP, "System Identifier", IMPACT_LEVEL]
|
|
942
|
+
else:
|
|
943
|
+
for index in range(len(instructions_df)):
|
|
944
|
+
if CSP in instructions_df.iloc[index].values:
|
|
945
|
+
instructions_df.columns = instructions_df.iloc[index]
|
|
946
|
+
instructions_df = instructions_df[index + 1 :]
|
|
947
|
+
break
|
|
948
|
+
# delete the rows before the found row
|
|
949
|
+
relevant_columns = [SYSTEM_NAME, CSP, IMPACT_LEVEL]
|
|
950
|
+
try:
|
|
951
|
+
instructions_df = instructions_df[relevant_columns]
|
|
952
|
+
except KeyError:
|
|
953
|
+
error_and_exit(
|
|
954
|
+
f"Unable to find the relevant columns in the Instructions worksheet. Do you have the correct "
|
|
955
|
+
f"revision set?\nRevision: {version}",
|
|
956
|
+
show_exec=False,
|
|
957
|
+
)
|
|
958
|
+
# convert the dataframe to a dictionary
|
|
959
|
+
return instructions_df.to_dict(orient="records")
|
|
960
|
+
|
|
961
|
+
|
|
962
|
+
def parse_and_map_data(
|
|
963
|
+
leveraged_auth_id: int, api: Api, ssp_id: int, cis_data: dict, crm_data: dict, version: Literal["rev5", "rev4"]
|
|
964
|
+
) -> None:
|
|
965
|
+
"""
|
|
966
|
+
Function to parse and map data from RegScale and the workbook.
|
|
967
|
+
|
|
968
|
+
:param int leveraged_auth_id: The leveraged authorization ID
|
|
969
|
+
:param Api api: RegScale API object
|
|
970
|
+
:param int ssp_id: RegScale SSP ID #
|
|
971
|
+
:param dict cis_data: Parsed CIS data to update the control implementations and objectives
|
|
972
|
+
:param dict crm_data: Parsed CRM data to update the control implementations and objectives
|
|
973
|
+
:param version: Literal["rev4", "rev5", "4", "5"],
|
|
974
|
+
:rtype: None
|
|
975
|
+
"""
|
|
976
|
+
with progress:
|
|
977
|
+
implementations = get_all_imps(api=api, ssp_id=ssp_id, cis_data=cis_data, version=version)
|
|
978
|
+
error_set = update_all_objectives(
|
|
979
|
+
leveraged_auth_id=leveraged_auth_id,
|
|
980
|
+
cis_data=cis_data,
|
|
981
|
+
crm_data=crm_data,
|
|
982
|
+
control_implementations=implementations,
|
|
983
|
+
version=version,
|
|
984
|
+
)
|
|
985
|
+
|
|
986
|
+
report(error_set)
|
|
987
|
+
|
|
988
|
+
|
|
989
|
+
def extract_control_name(control_string: str) -> str:
|
|
990
|
+
"""
|
|
991
|
+
Extracts the control name (e.g., 'AC-20(1)') from a given string.
|
|
992
|
+
|
|
993
|
+
:param str control_string: The string to extract the control name from
|
|
994
|
+
:return: The extracted control name
|
|
995
|
+
:rtype: str
|
|
996
|
+
"""
|
|
997
|
+
pattern = r"^[A-Za-z]{2}-\d{1,3}(?:\(\d+\))?"
|
|
998
|
+
match = re.match(pattern, control_string.upper())
|
|
999
|
+
return match.group() if match else ""
|
|
1000
|
+
|
|
1001
|
+
|
|
1002
|
+
def rev_4_map(control_id: str) -> Optional[str]:
|
|
1003
|
+
"""
|
|
1004
|
+
Maps a control ID to its corresponding revision 4 control ID.
|
|
1005
|
+
|
|
1006
|
+
:param str control_id: The control ID to map
|
|
1007
|
+
:return: The mapped control ID or None if not found
|
|
1008
|
+
:rtype: Optional[str]
|
|
1009
|
+
"""
|
|
1010
|
+
# Regex pattern to match different control ID formats
|
|
1011
|
+
pattern = r"^([A-Z]{2})-(\d{2})\s*(?:\((\d{2})\))?\s*(?:\(([a-z])\))?$"
|
|
1012
|
+
|
|
1013
|
+
match = re.match(pattern, control_id, re.IGNORECASE)
|
|
1014
|
+
|
|
1015
|
+
if not match:
|
|
1016
|
+
return None
|
|
1017
|
+
|
|
1018
|
+
# Extract components
|
|
1019
|
+
prefix, number, subnum, letter = match.groups()
|
|
1020
|
+
|
|
1021
|
+
# Convert to lowercase
|
|
1022
|
+
prefix = prefix.lower()
|
|
1023
|
+
|
|
1024
|
+
# Construct statement ID
|
|
1025
|
+
if subnum:
|
|
1026
|
+
# With sub-number
|
|
1027
|
+
base_id = f"{prefix}-{number}.{int(subnum)}_smt"
|
|
1028
|
+
return f"{base_id}{f'.{letter}' if letter else ''}"
|
|
1029
|
+
else:
|
|
1030
|
+
# Without sub-number
|
|
1031
|
+
base_id = f"{prefix}-{number}_smt"
|
|
1032
|
+
return f"{base_id}{f'.{letter}' if letter else ''}"
|
|
1033
|
+
|
|
1034
|
+
|
|
1035
|
+
def parse_and_import_ciscrm(
|
|
1036
|
+
file_path: click.Path,
|
|
1037
|
+
version: Literal["rev4", "rev5", "4", "5"],
|
|
1038
|
+
cis_sheet_name: str,
|
|
1039
|
+
crm_sheet_name: str,
|
|
1040
|
+
regscale_ssp_id: int,
|
|
1041
|
+
leveraged_auth_id: int = 0,
|
|
1042
|
+
) -> None:
|
|
1043
|
+
"""
|
|
1044
|
+
Parse and import the FedRAMP Rev5 CIS/CRM Workbook into a RegScale System Security Plan
|
|
1045
|
+
|
|
1046
|
+
:param click.Path file_path: The file path to the FedRAMP CIS CRM .xlsx file
|
|
1047
|
+
:param Literal["rev4", "rev5"] version: FedRAMP revision version
|
|
1048
|
+
:param str cis_sheet_name: CIS sheet name in the FedRAMP CIS CRM .xlsx to parse
|
|
1049
|
+
:param str crm_sheet_name: CRM sheet name in the FedRAMP CIS CRM .xlsx to parse
|
|
1050
|
+
:param int regscale_ssp_id: The ID number from RegScale of the System Security Plan
|
|
1051
|
+
:param int leveraged_auth_id: RegScale Leveraged Authorization ID #, if none provided, one will be created
|
|
1052
|
+
:raises ValueError: If the SSP with the given ID is not found in RegScale
|
|
1053
|
+
:rtype: None
|
|
1054
|
+
"""
|
|
1055
|
+
sys_name_key = "System Name"
|
|
1056
|
+
api = Api()
|
|
1057
|
+
ssp: SecurityPlan = SecurityPlan.get_object(regscale_ssp_id)
|
|
1058
|
+
if not ssp:
|
|
1059
|
+
raise ValueError(f"SSP with ID {regscale_ssp_id} not found in RegScale.")
|
|
1060
|
+
|
|
1061
|
+
if "5" in version:
|
|
1062
|
+
version = "rev5"
|
|
1063
|
+
part_mapper_rev5.load_fedramp_version_5_mapping()
|
|
1064
|
+
else:
|
|
1065
|
+
version = "rev4"
|
|
1066
|
+
part_mapper_rev4.load_fedramp_version_4_mapping()
|
|
1067
|
+
# parse the instructions worksheet to get the csp name, system name, and other data
|
|
1068
|
+
instructions_data = parse_instructions_worksheet(file_path=file_path, version=version) # type: ignore
|
|
1069
|
+
|
|
1070
|
+
# get the system names from the instructions data by dropping any non-string values
|
|
1071
|
+
system_names = [entry[sys_name_key] for entry in instructions_data if isinstance(entry[sys_name_key], str)]
|
|
1072
|
+
name_match: str = system_names[0]
|
|
1073
|
+
|
|
1074
|
+
# update the instructions data to the matched system names
|
|
1075
|
+
instructions_data = [
|
|
1076
|
+
(
|
|
1077
|
+
entry
|
|
1078
|
+
if isinstance(entry[sys_name_key], str)
|
|
1079
|
+
and entry[sys_name_key] == name_match
|
|
1080
|
+
or entry[sys_name_key] == ssp.systemName
|
|
1081
|
+
else None
|
|
1082
|
+
)
|
|
1083
|
+
for entry in instructions_data
|
|
1084
|
+
]
|
|
1085
|
+
# remove any None values from the instructions data
|
|
1086
|
+
instructions_data = [entry for entry in instructions_data if entry][0]
|
|
1087
|
+
if not any(instructions_data):
|
|
1088
|
+
raise ValueError("Unable to parse data from Instructions sheet.")
|
|
1089
|
+
|
|
1090
|
+
# start parsing the workbook
|
|
1091
|
+
cis_data = parse_cis_worksheet(file_path=file_path, cis_sheet_name=cis_sheet_name)
|
|
1092
|
+
crm_data = {}
|
|
1093
|
+
if crm_sheet_name:
|
|
1094
|
+
crm_data = parse_crm_worksheet(
|
|
1095
|
+
file_path=file_path, crm_sheet_name=crm_sheet_name, version=version # type: ignore
|
|
1096
|
+
)
|
|
1097
|
+
if leveraged_auth_id == 0:
|
|
1098
|
+
auths = LeveragedAuthorization.get_all_by_parent(ssp.id)
|
|
1099
|
+
if auths:
|
|
1100
|
+
leveraged_auth_id = next((auth.id for auth in auths))
|
|
1101
|
+
else:
|
|
1102
|
+
leveraged_auth_id = new_leveraged_auth(
|
|
1103
|
+
ssp=ssp,
|
|
1104
|
+
user_id=api.config["userId"],
|
|
1105
|
+
instructions_data=instructions_data,
|
|
1106
|
+
version=version, # type: ignore
|
|
1107
|
+
)
|
|
1108
|
+
# Update objectives using the mapped data using threads
|
|
1109
|
+
parse_and_map_data(
|
|
1110
|
+
leveraged_auth_id=leveraged_auth_id,
|
|
1111
|
+
api=api,
|
|
1112
|
+
ssp_id=regscale_ssp_id,
|
|
1113
|
+
cis_data=cis_data,
|
|
1114
|
+
crm_data=crm_data,
|
|
1115
|
+
version=version, # type: ignore
|
|
1116
|
+
)
|
|
1117
|
+
|
|
1118
|
+
# upload workbook to the SSP
|
|
1119
|
+
File.upload_file_to_regscale(
|
|
1120
|
+
file_name=str(file_path),
|
|
1121
|
+
parent_id=regscale_ssp_id,
|
|
1122
|
+
parent_module="securityplans",
|
|
1123
|
+
api=api,
|
|
1124
|
+
)
|