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,522 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""A class to import Fedramp V4 and V5 POAMs"""
|
|
4
|
+
import logging
|
|
5
|
+
import re
|
|
6
|
+
from collections import Counter
|
|
7
|
+
from typing import Optional, Union
|
|
8
|
+
|
|
9
|
+
import rich.progress
|
|
10
|
+
from openpyxl import Workbook, load_workbook # type: ignore
|
|
11
|
+
from openpyxl.compat import safe_string
|
|
12
|
+
from openpyxl.utils import column_index_from_string # type: ignore
|
|
13
|
+
from openpyxl.utils.exceptions import InvalidFileException # type: ignore
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
from regscale.core.app.utils.app_utils import create_progress_object, get_current_datetime
|
|
17
|
+
from regscale.core.utils.date import date_str, datetime_str
|
|
18
|
+
from regscale.integrations.integration.issue import IntegrationIssue
|
|
19
|
+
from regscale.integrations.scanner_integration import issue_due_date
|
|
20
|
+
from regscale.integrations.variables import ScannerVariables
|
|
21
|
+
from regscale.models import IssueSeverity, regscale_models
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger("regscale")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class POAM(IntegrationIssue):
|
|
27
|
+
"""
|
|
28
|
+
Custom Integration issue class
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, file_path: str, module: str, module_id: int, poam_id_header: str = "POAM ID"):
|
|
32
|
+
super().__init__()
|
|
33
|
+
self.skipped_records = 0
|
|
34
|
+
self.blank_records = 0
|
|
35
|
+
self.blank_threshold = 3
|
|
36
|
+
self.error_records = 0
|
|
37
|
+
self.progress = create_progress_object()
|
|
38
|
+
if not file_path:
|
|
39
|
+
raise ValueError("File path is required")
|
|
40
|
+
self.file_path = Path(file_path)
|
|
41
|
+
self.module = module
|
|
42
|
+
self.module_id = module_id
|
|
43
|
+
self.poam_id_header = poam_id_header
|
|
44
|
+
self.poam_data: dict[str, regscale_models.Issue] = {}
|
|
45
|
+
data = self.import_poam()
|
|
46
|
+
self.data = data
|
|
47
|
+
|
|
48
|
+
self.create_or_update_issues(
|
|
49
|
+
issues=list(self.poam_data.values()), parent_id=self.module_id, parent_module=self.module
|
|
50
|
+
)
|
|
51
|
+
logger.info("Finished importing POAMs..")
|
|
52
|
+
|
|
53
|
+
def create_or_update_issues(
|
|
54
|
+
self,
|
|
55
|
+
issues: list[regscale_models.Issue],
|
|
56
|
+
parent_id: int,
|
|
57
|
+
parent_module: str,
|
|
58
|
+
):
|
|
59
|
+
"""
|
|
60
|
+
Create issues in RegScale
|
|
61
|
+
|
|
62
|
+
:param list[Issue] issues: list of issues to create or update
|
|
63
|
+
:param int parent_id: parent id
|
|
64
|
+
:param str parent_module: parent module
|
|
65
|
+
"""
|
|
66
|
+
with self.progress as progress:
|
|
67
|
+
issue_task = progress.add_task(
|
|
68
|
+
"[#f8b737]Determining if issues need to be updated or created...", total=len(issues)
|
|
69
|
+
)
|
|
70
|
+
issue_updates = []
|
|
71
|
+
issue_creations = []
|
|
72
|
+
for issue in issues:
|
|
73
|
+
issue.parentId = parent_id
|
|
74
|
+
issue.parentModule = parent_module
|
|
75
|
+
if issue.id != 0:
|
|
76
|
+
issue_updates.append(issue)
|
|
77
|
+
else:
|
|
78
|
+
issue_creations.append(issue)
|
|
79
|
+
progress.update(issue_task, advance=1)
|
|
80
|
+
if issue_creations:
|
|
81
|
+
regscale_models.Issue.batch_create(issue_creations, self.progress)
|
|
82
|
+
if issue_updates:
|
|
83
|
+
regscale_models.Issue.batch_update(issue_updates, self.progress)
|
|
84
|
+
|
|
85
|
+
def pull(self):
|
|
86
|
+
"""
|
|
87
|
+
Pull inventory from an Integration platform into RegScale
|
|
88
|
+
"""
|
|
89
|
+
# Implement the pull method here
|
|
90
|
+
pass
|
|
91
|
+
|
|
92
|
+
def file_type(self):
|
|
93
|
+
"""
|
|
94
|
+
A method to return the file type
|
|
95
|
+
"""
|
|
96
|
+
file_type = None
|
|
97
|
+
if self.file_path:
|
|
98
|
+
file_type = self.file_path.suffix
|
|
99
|
+
return file_type
|
|
100
|
+
|
|
101
|
+
@staticmethod
|
|
102
|
+
def get_index_from_column_name(column_name: str) -> int:
|
|
103
|
+
"""
|
|
104
|
+
A method to get the index from a column name
|
|
105
|
+
|
|
106
|
+
:param str column_name: A column name
|
|
107
|
+
:return: The index of the column
|
|
108
|
+
:rtype: int
|
|
109
|
+
"""
|
|
110
|
+
return column_index_from_string(column_name) - 1
|
|
111
|
+
|
|
112
|
+
def get_row_val(self, row: tuple, column_name: str) -> Optional[str]:
|
|
113
|
+
"""
|
|
114
|
+
Get the value from the row
|
|
115
|
+
|
|
116
|
+
:param tuple row: The row
|
|
117
|
+
:param str column_name: The column name
|
|
118
|
+
:return: The value or None
|
|
119
|
+
:rtype: Optional[str]
|
|
120
|
+
"""
|
|
121
|
+
try:
|
|
122
|
+
index = self.get_index_from_column_name(column_name)
|
|
123
|
+
return row[index] if index < len(row) else None
|
|
124
|
+
except Exception as e:
|
|
125
|
+
logger.error(f"Error getting value for column {column_name}: {str(e)}")
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
def get_basis_for_adjustment(self, row: tuple) -> Optional[str]:
|
|
129
|
+
"""
|
|
130
|
+
Get the basis for adjustment
|
|
131
|
+
|
|
132
|
+
:param tuple row: The row
|
|
133
|
+
:return: The basis for adjustment or None if adjusted risk rating is the same as risk rating
|
|
134
|
+
:rtype: Optional[str]
|
|
135
|
+
"""
|
|
136
|
+
basis_for_adjustment = self.empty(row[23])
|
|
137
|
+
risk_rating = self.get_row_val(row, "S")
|
|
138
|
+
adjusted_risk_rating = self.get_row_val(row, "T")
|
|
139
|
+
if (adjusted_risk_rating != risk_rating) and not basis_for_adjustment:
|
|
140
|
+
return "POAM Import"
|
|
141
|
+
if adjusted_risk_rating == risk_rating:
|
|
142
|
+
return None
|
|
143
|
+
return basis_for_adjustment
|
|
144
|
+
|
|
145
|
+
def process_cve(self, cve: Optional[str], index: int, sheet: str) -> Optional[str]:
|
|
146
|
+
"""
|
|
147
|
+
Process and validate CVE string.
|
|
148
|
+
|
|
149
|
+
:param Optional[str] cve: The CVE string to process
|
|
150
|
+
:param int index: The row index for logging purposes
|
|
151
|
+
:param str sheet: The sheet name for logging purposes
|
|
152
|
+
:return: Processed CVE string or None
|
|
153
|
+
:rtype: Optional[str]
|
|
154
|
+
"""
|
|
155
|
+
cve = self.empty(cve)
|
|
156
|
+
if not cve:
|
|
157
|
+
return None
|
|
158
|
+
|
|
159
|
+
cve_pattern = r".*CVE-\d{4}-\d{4,7}.*"
|
|
160
|
+
match = re.match(cve_pattern, cve, re.IGNORECASE)
|
|
161
|
+
if match:
|
|
162
|
+
cve_match = re.search(r"CVE-\d{4}-\d{4,7}", cve, re.IGNORECASE)
|
|
163
|
+
if cve_match:
|
|
164
|
+
return cve_match.group(0).upper() # Ensure consistent formatting
|
|
165
|
+
return None # No CVE found within the matching string
|
|
166
|
+
else:
|
|
167
|
+
logger.warning(f"Invalid CVE format: {cve} on row {index}, sheet {sheet}. Setting to empty string.")
|
|
168
|
+
return ""
|
|
169
|
+
|
|
170
|
+
def gen_issue_from_row(
|
|
171
|
+
self, row: tuple, status: str, category: str, index: int, sheet: str
|
|
172
|
+
) -> Optional[regscale_models.Issue]:
|
|
173
|
+
"""
|
|
174
|
+
Generate an Issue object from a row in the POAM spreadsheet.
|
|
175
|
+
|
|
176
|
+
:param tuple row: A row from the POAM spreadsheet
|
|
177
|
+
:param str status: The status of the issue (Open or Closed)
|
|
178
|
+
:param str category: The category of the issue
|
|
179
|
+
:param int index: The index of the row in the spreadsheet
|
|
180
|
+
:param str sheet: The name of the sheet being processed
|
|
181
|
+
:return: An Issue object if successfully generated, None otherwise
|
|
182
|
+
:rtype: Optional[Issue]
|
|
183
|
+
"""
|
|
184
|
+
# Extract and validate key fields
|
|
185
|
+
poam_id = self.get_row_val(row, "A")
|
|
186
|
+
weakness_name = str(self.get_row_val(row, "C"))
|
|
187
|
+
|
|
188
|
+
if not poam_id or not poam_id.upper():
|
|
189
|
+
logger.warning(f"Invalid POAM ID on row {index}, sheet {sheet}. Skipping.")
|
|
190
|
+
return None
|
|
191
|
+
if not weakness_name:
|
|
192
|
+
logger.warning(f"Title is required on row {index}, sheet {sheet}. Unable to import")
|
|
193
|
+
return None
|
|
194
|
+
|
|
195
|
+
# Process risk ratings and adjustments
|
|
196
|
+
original_risk_rating = self.empty(self.get_row_val(row, "S"))
|
|
197
|
+
adjusted_risk_rating = self.get_row_val(row, "T")
|
|
198
|
+
adjusted_risk_rating = original_risk_rating or "N/A" if adjusted_risk_rating == "N/A" else adjusted_risk_rating
|
|
199
|
+
|
|
200
|
+
# Process CVE
|
|
201
|
+
cve = self.process_cve(self.get_row_val(row, "AD"), index, sheet)
|
|
202
|
+
|
|
203
|
+
# Determine severity level
|
|
204
|
+
severity_level = getattr(IssueSeverity, category.title(), IssueSeverity.NotAssigned)
|
|
205
|
+
|
|
206
|
+
# Process dates
|
|
207
|
+
date_created = date_str(self.get_row_val(row, "K"))
|
|
208
|
+
date_last_updated = datetime_str(self.get_row_val(row, "O"))
|
|
209
|
+
due_date = self.get_row_val(row, "L")
|
|
210
|
+
if due_date == "#REF!":
|
|
211
|
+
due_date = ""
|
|
212
|
+
due_date = date_str(due_date) or issue_due_date(severity_level, date_created, high=30, moderate=90, low=364)
|
|
213
|
+
date_completed = None
|
|
214
|
+
# Create and return the Issue object
|
|
215
|
+
try:
|
|
216
|
+
if status == "Closed":
|
|
217
|
+
date_completed = date_str(date_last_updated) or due_date or get_current_datetime()
|
|
218
|
+
|
|
219
|
+
issue: regscale_models.Issue = regscale_models.Issue(
|
|
220
|
+
integrationFindingId=poam_id,
|
|
221
|
+
otherIdentifier=poam_id,
|
|
222
|
+
dateCreated=date_created,
|
|
223
|
+
dateLastUpdated=date_last_updated,
|
|
224
|
+
title=weakness_name[:255],
|
|
225
|
+
description=self.get_row_val(row, "D"),
|
|
226
|
+
status=status,
|
|
227
|
+
severityLevel=severity_level,
|
|
228
|
+
assetIdentifier=self.get_row_val(row, "G"),
|
|
229
|
+
isPoam=True,
|
|
230
|
+
issueOwnerId=ScannerVariables.userId,
|
|
231
|
+
securityPlanId=self.module_id if self.module == "securityplans" else 0,
|
|
232
|
+
cve=cve,
|
|
233
|
+
sourceReport=self.get_row_val(row, "E"),
|
|
234
|
+
pluginId=str(self.get_row_val(row, "F")),
|
|
235
|
+
autoApproved="No",
|
|
236
|
+
dueDate=due_date,
|
|
237
|
+
parentId=self.module_id, # type: ignore
|
|
238
|
+
parentModule=self.module, # type: ignore
|
|
239
|
+
basisForAdjustment=self.get_basis_for_adjustment(row),
|
|
240
|
+
dateCompleted=date_completed, # when an issue is closed it has to have a date completed cannot be null
|
|
241
|
+
manualDetectionSource=self.get_row_val(row, "E"),
|
|
242
|
+
manualDetectionId=str(self.get_row_val(row, "F")),
|
|
243
|
+
changes=safe_string(self.get_row_val(row, "N")),
|
|
244
|
+
poamComments=self.empty(self.get_row_val(row, "Z")),
|
|
245
|
+
deviationRationale=self.empty(self.get_row_val(row, "X")),
|
|
246
|
+
remediationDescription=self.empty(self.get_row_val(row, "J")),
|
|
247
|
+
vendorDependency=self.empty(self.get_row_val(row, "P")),
|
|
248
|
+
vendorLastUpdate=self.empty(date_str(self.get_row_val(row, "Q"))),
|
|
249
|
+
vendorName=self.empty(self.get_row_val(row, "R")),
|
|
250
|
+
adjustedRiskRating=adjusted_risk_rating,
|
|
251
|
+
originalRiskRating=original_risk_rating or adjusted_risk_rating,
|
|
252
|
+
falsePositive=self.set_false_positive(row),
|
|
253
|
+
identification="Vulnerability Assessment",
|
|
254
|
+
operationalRequirement=self.set_operational_requirement(row),
|
|
255
|
+
dateFirstDetected=date_str(self.get_row_val(row, "K")),
|
|
256
|
+
riskAdjustment=self.set_risk_adjustment(row),
|
|
257
|
+
).create_or_update(bulk_update=True)
|
|
258
|
+
if poc := self.get_row_val(row, "H"):
|
|
259
|
+
_ = regscale_models.Property(
|
|
260
|
+
key="POC",
|
|
261
|
+
value=poc,
|
|
262
|
+
parentId=issue.id,
|
|
263
|
+
parentModule="issues",
|
|
264
|
+
).create_or_update(bulk_update=True, bulk_create=True)
|
|
265
|
+
except Exception as e:
|
|
266
|
+
logger.error(f"Error creating Issue object on row {index}, sheet {sheet}: {str(e)}", exc_info=True)
|
|
267
|
+
return None
|
|
268
|
+
|
|
269
|
+
self.poam_data[poam_id] = issue
|
|
270
|
+
return issue
|
|
271
|
+
|
|
272
|
+
def import_poam(self) -> Optional[Workbook]:
|
|
273
|
+
"""
|
|
274
|
+
Import POAM data from the workbook.
|
|
275
|
+
|
|
276
|
+
:return: The processed workbook or None if import failed
|
|
277
|
+
:rtype: Optional[Workbook]
|
|
278
|
+
"""
|
|
279
|
+
try:
|
|
280
|
+
workbook = load_workbook(filename=self.file_path, data_only=True, read_only=True)
|
|
281
|
+
except (FileNotFoundError, InvalidFileException) as e:
|
|
282
|
+
logger.error(f"Failed to load workbook: {e}")
|
|
283
|
+
return None
|
|
284
|
+
|
|
285
|
+
poam_sheets = [sheet for sheet in workbook.sheetnames if re.search("POA&M Items", sheet)]
|
|
286
|
+
|
|
287
|
+
with self.progress as progress:
|
|
288
|
+
parsing_progress = progress.add_task("[#f8b737]Parsing data from workbook...", total=len(poam_sheets))
|
|
289
|
+
|
|
290
|
+
for sheet in poam_sheets:
|
|
291
|
+
self.process_sheet(workbook[sheet], sheet, progress)
|
|
292
|
+
progress.update(parsing_progress, advance=1)
|
|
293
|
+
|
|
294
|
+
self.count_issues_by_status()
|
|
295
|
+
return workbook
|
|
296
|
+
|
|
297
|
+
def process_sheet(self, ws, sheet_name: str, progress: rich.progress.Progress):
|
|
298
|
+
"""
|
|
299
|
+
Process a single sheet in the POAM workbook.
|
|
300
|
+
|
|
301
|
+
:param ws: The worksheet object
|
|
302
|
+
:param str sheet_name: The name of the sheet
|
|
303
|
+
:param rich.progress.Progress progress: The progress object for updating task progress
|
|
304
|
+
"""
|
|
305
|
+
category = ws["C3"].value or "Low"
|
|
306
|
+
if not ws["C3"].value:
|
|
307
|
+
logger.warning(f"Category is required in cell C3. Defaulting to Low import for sheet {sheet_name}.")
|
|
308
|
+
if not category:
|
|
309
|
+
logger.warning(f"Category is required in cell C3. Skipping import for sheet {sheet_name}.")
|
|
310
|
+
return
|
|
311
|
+
|
|
312
|
+
status = self.determine_status(sheet_name)
|
|
313
|
+
if status is None:
|
|
314
|
+
logger.warning(f"Unable to determine POA&M status for sheet {sheet_name}. Skipping import.")
|
|
315
|
+
return
|
|
316
|
+
|
|
317
|
+
start_row = self.find_start_row(ws)
|
|
318
|
+
if start_row is None:
|
|
319
|
+
logger.warning(f"No POAM entries found in sheet {sheet_name}. Skipping.")
|
|
320
|
+
return
|
|
321
|
+
|
|
322
|
+
parsing_poams = progress.add_task(
|
|
323
|
+
f"[#ef5d23]Parsing '{sheet_name}' sheet for POAMs...", total=ws.max_row - start_row + 1
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
for index, row in enumerate(ws.iter_rows(min_row=start_row, values_only=True), start_row):
|
|
327
|
+
try:
|
|
328
|
+
self.process_row(row, status, category, index, sheet_name)
|
|
329
|
+
if self.blank_records >= self.blank_threshold:
|
|
330
|
+
logger.warning("Too many empty records skipped. Stopping import.")
|
|
331
|
+
progress.update(parsing_poams, completed=ws.max_row - start_row + 1)
|
|
332
|
+
break
|
|
333
|
+
except Exception as e:
|
|
334
|
+
logger.error(f"Error processing row {index} in sheet {sheet_name}: {str(e)}")
|
|
335
|
+
self.error_records += 1
|
|
336
|
+
progress.update(parsing_poams, advance=1)
|
|
337
|
+
regscale_models.Issue.bulk_save(progress_context=progress)
|
|
338
|
+
regscale_models.Property.bulk_save(progress_context=progress)
|
|
339
|
+
|
|
340
|
+
def find_start_row(self, ws) -> Optional[int]:
|
|
341
|
+
"""
|
|
342
|
+
Find the first row with 'V-' or any identifier-number in column A.
|
|
343
|
+
|
|
344
|
+
:param ws: The worksheet object
|
|
345
|
+
:return: The row number where POAM entries start, or None if not found
|
|
346
|
+
:rtype: Optional[int]
|
|
347
|
+
"""
|
|
348
|
+
for row_index, row in enumerate(ws.iter_rows(min_row=1, max_col=1, values_only=True), 1):
|
|
349
|
+
if row[0] and self.poam_id_header in str(row[0]):
|
|
350
|
+
logger.info(f"Found POAM header parsing data from row {row_index + 1}")
|
|
351
|
+
return row_index + 1
|
|
352
|
+
return None
|
|
353
|
+
|
|
354
|
+
@staticmethod
|
|
355
|
+
def identify_id_data(value: str) -> bool:
|
|
356
|
+
"""
|
|
357
|
+
Identify the ID
|
|
358
|
+
|
|
359
|
+
:param str value: The value
|
|
360
|
+
:return: The ID
|
|
361
|
+
:rtype: bool
|
|
362
|
+
"""
|
|
363
|
+
return bool(re.match(r".+-\d+$", value))
|
|
364
|
+
|
|
365
|
+
@staticmethod
|
|
366
|
+
def determine_status(sheet: str) -> Optional[str]:
|
|
367
|
+
"""
|
|
368
|
+
Determine the status based on sheet name.
|
|
369
|
+
|
|
370
|
+
:param str sheet: The name of the sheet
|
|
371
|
+
:return: The status of the POA&M (Closed, Open, or None)
|
|
372
|
+
:rtype: Optional[str]
|
|
373
|
+
"""
|
|
374
|
+
# Check if the sheet name contains 'closed' (case-insensitive)
|
|
375
|
+
if "closed" in sheet.lower():
|
|
376
|
+
return "Closed"
|
|
377
|
+
# Check if the sheet name contains 'open' (case-insensitive)
|
|
378
|
+
elif "open" in sheet.lower():
|
|
379
|
+
return "Open"
|
|
380
|
+
# If neither 'closed' nor 'open' is found in the sheet name
|
|
381
|
+
else:
|
|
382
|
+
# Log a warning message
|
|
383
|
+
logger.debug(f"Unable to determine POA&M status for sheet {sheet}. Skipping import.")
|
|
384
|
+
# Return None to indicate that the status couldn't be determined
|
|
385
|
+
return None
|
|
386
|
+
|
|
387
|
+
def process_row(self, row: tuple, status: str, category: str, index: int, sheet: str):
|
|
388
|
+
"""
|
|
389
|
+
Process a single row of the POAM sheet.
|
|
390
|
+
|
|
391
|
+
:param tuple row: The row data from the POAM sheet
|
|
392
|
+
:param str status: The status of the POAM (Open or Closed)
|
|
393
|
+
:param str category: The category of the POAM
|
|
394
|
+
:param int index: The index of the current row
|
|
395
|
+
:param str sheet: The name of the current sheet
|
|
396
|
+
"""
|
|
397
|
+
try:
|
|
398
|
+
# Get the POAM ID from column A and handle empty values
|
|
399
|
+
poam_id = self.empty(self.get_row_val(row, "A"))
|
|
400
|
+
|
|
401
|
+
# Check if POAM ID is missing
|
|
402
|
+
if not poam_id:
|
|
403
|
+
logger.warning(f"POAM ID is required. Skipping import for row {index} in sheet {sheet}.")
|
|
404
|
+
self.blank_records += 1
|
|
405
|
+
return
|
|
406
|
+
self.blank_records = 0
|
|
407
|
+
# Check if closed POAM already exists in the data
|
|
408
|
+
if status == "Closed" and poam_id in self.poam_data:
|
|
409
|
+
logger.warning(
|
|
410
|
+
f"POAM {poam_id} already exists with status {status}. Skipping import for row {index} in sheet {sheet}."
|
|
411
|
+
)
|
|
412
|
+
self.skipped_records += 1
|
|
413
|
+
return
|
|
414
|
+
|
|
415
|
+
# Generate issue from row data
|
|
416
|
+
issue = self.gen_issue_from_row(row, status, category, index, sheet)
|
|
417
|
+
if issue:
|
|
418
|
+
# Add the generated issue to the POAM data dictionary
|
|
419
|
+
self.poam_data[poam_id] = issue
|
|
420
|
+
else:
|
|
421
|
+
logger.warning(f"Failed to generate issue for POAM {poam_id} from row {index} in sheet {sheet}")
|
|
422
|
+
self.skipped_records += 151
|
|
423
|
+
except Exception as e:
|
|
424
|
+
logger.error(f"Error in process_row for row {index} in sheet {sheet}: {str(e)}")
|
|
425
|
+
self.error_records += 1
|
|
426
|
+
|
|
427
|
+
def count_issues_by_status(self):
|
|
428
|
+
"""
|
|
429
|
+
A method to count the issues and log the counts.
|
|
430
|
+
"""
|
|
431
|
+
status_list = [issue.status for issue in self.poam_data.values() if issue]
|
|
432
|
+
status_counts = Counter(status_list)
|
|
433
|
+
logger.info(
|
|
434
|
+
"Found %i issues in the POAM Workbook, %i Open and %i Closed.",
|
|
435
|
+
len(self.poam_data),
|
|
436
|
+
status_counts["Open"],
|
|
437
|
+
status_counts["Closed"],
|
|
438
|
+
)
|
|
439
|
+
error_msg = f"Skipped {self.skipped_records} records, {self.error_records} errors"
|
|
440
|
+
if self.error_records:
|
|
441
|
+
logger.error(error_msg)
|
|
442
|
+
elif self.skipped_records:
|
|
443
|
+
logger.warning(error_msg)
|
|
444
|
+
else:
|
|
445
|
+
logger.info(error_msg)
|
|
446
|
+
|
|
447
|
+
@staticmethod
|
|
448
|
+
def empty(string: Optional[str]) -> Union[str, None]:
|
|
449
|
+
"""
|
|
450
|
+
A method to empty the data
|
|
451
|
+
|
|
452
|
+
:param str string: A string
|
|
453
|
+
:return: None if the string is 'None' or the input is not a string
|
|
454
|
+
:rtype: Union[str, None]
|
|
455
|
+
"""
|
|
456
|
+
if not isinstance(string, str):
|
|
457
|
+
return None
|
|
458
|
+
|
|
459
|
+
if string.lower() in ["none", "n/a"]:
|
|
460
|
+
return None
|
|
461
|
+
|
|
462
|
+
return string
|
|
463
|
+
|
|
464
|
+
def set_false_positive(self, row: tuple) -> str:
|
|
465
|
+
"""
|
|
466
|
+
Set the false positive value
|
|
467
|
+
|
|
468
|
+
:param tuple row: The row
|
|
469
|
+
:return: The false positive value
|
|
470
|
+
:rtype: str
|
|
471
|
+
"""
|
|
472
|
+
# Map lowercased values to their corresponding responses
|
|
473
|
+
value_map = {"yes": "Yes", "no": "No", "pending": "Pending Review"}
|
|
474
|
+
|
|
475
|
+
# Get the value from the row and convert it to lowercase
|
|
476
|
+
if row_value := self.get_row_val_str(row, "V"):
|
|
477
|
+
row_value = row_value.lower()
|
|
478
|
+
|
|
479
|
+
# Get the corresponding response from the map, default to 'No' if not found
|
|
480
|
+
return value_map.get(row_value, "No") if row_value else "No"
|
|
481
|
+
|
|
482
|
+
def set_operational_requirement(self, row: tuple) -> str:
|
|
483
|
+
"""
|
|
484
|
+
Set the operational requirement value
|
|
485
|
+
|
|
486
|
+
:param tuple row: The row
|
|
487
|
+
:return: The operational requirement value
|
|
488
|
+
:rtype: str
|
|
489
|
+
"""
|
|
490
|
+
# Map lowercased values to their corresponding responses
|
|
491
|
+
value_map = {"yes": "Yes", "no": "No", "pending": "Pending"}
|
|
492
|
+
|
|
493
|
+
# Get the value from the row and convert it to lowercase
|
|
494
|
+
if row_value := self.get_row_val_str(row, "W"):
|
|
495
|
+
row_value = row_value.lower()
|
|
496
|
+
|
|
497
|
+
# Get the corresponding response from the map, default to No if not found
|
|
498
|
+
return value_map.get(row_value, "No") if row_value else "No"
|
|
499
|
+
|
|
500
|
+
def set_risk_adjustment(self, row: tuple) -> str:
|
|
501
|
+
"""
|
|
502
|
+
Set the risk adjustment value
|
|
503
|
+
|
|
504
|
+
:param tuple row: The row
|
|
505
|
+
:return: The Risk adjustment string
|
|
506
|
+
:rtype: str
|
|
507
|
+
"""
|
|
508
|
+
value_map = {"yes": "Yes", "no": "No", "pending": "Pending"}
|
|
509
|
+
if row_value := self.get_row_val_str(row, "U"):
|
|
510
|
+
row_value = row_value.lower()
|
|
511
|
+
return value_map.get(row_value, "No") if row_value else "No"
|
|
512
|
+
|
|
513
|
+
def get_row_val_str(self, row: tuple, column_name: str) -> str:
|
|
514
|
+
"""
|
|
515
|
+
Get the safe string
|
|
516
|
+
|
|
517
|
+
:param tuple row: The row
|
|
518
|
+
:param str column_name: The column name
|
|
519
|
+
:return: The safe string
|
|
520
|
+
:rtype: str
|
|
521
|
+
"""
|
|
522
|
+
return safe_string(self.get_row_val(row, column_name))
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import List, Optional, Tuple
|
|
3
|
+
|
|
4
|
+
from regscale.core.decorators import singleton
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@singleton
|
|
8
|
+
class PartMapper:
|
|
9
|
+
"""
|
|
10
|
+
PartMapper class Standardized approach to mapping identifiers between control id in FedRAMP and other frameworks
|
|
11
|
+
|
|
12
|
+
# Example usage
|
|
13
|
+
mapper = PartMapper()
|
|
14
|
+
mapper.load_fedramp_version_5_mapping() or mapper.load_json_from_file("path/to/fedramp_r5_parts.json")
|
|
15
|
+
control_label_and_part_results = mapper.find_by_control_label_and_part("AC-1", "a1")
|
|
16
|
+
oscal_control_id_and_part_results = mapper.find_by_oscal_control_id_and_part("ac-1", "a1")
|
|
17
|
+
print("Results for control label 'AC-1' and part 'a1':", control_label_and_part_results)
|
|
18
|
+
print("Results for OSCAL control ID 'ac-1' and part 'a1':", oscal_control_id_and_part_results)
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self):
|
|
22
|
+
self.data = []
|
|
23
|
+
|
|
24
|
+
def find_by_source(self, source: str) -> Optional[str]:
|
|
25
|
+
"""
|
|
26
|
+
Find a mapping by source.
|
|
27
|
+
:param str source: The source.
|
|
28
|
+
:return: A str of the oscal part identifier or null.
|
|
29
|
+
:rtype: Optional[str]
|
|
30
|
+
"""
|
|
31
|
+
result = None
|
|
32
|
+
for item in self.data:
|
|
33
|
+
if str(item.get("SOURCE")).strip() == source:
|
|
34
|
+
result = item.get("OSCAL_PART_IDENTIFIER")
|
|
35
|
+
return result
|
|
36
|
+
return result
|
|
37
|
+
|
|
38
|
+
def find_sub_parts(self, source: str) -> List[str]:
|
|
39
|
+
"""
|
|
40
|
+
Find a mapping by source.
|
|
41
|
+
:param str source: The source.
|
|
42
|
+
:return: A list of sub-parts.
|
|
43
|
+
:rtype: List[str]
|
|
44
|
+
"""
|
|
45
|
+
result = []
|
|
46
|
+
for item in self.data:
|
|
47
|
+
if str(item.get("SOURCE")).strip() == source:
|
|
48
|
+
parts = item.get("SUB_PARTS", [])
|
|
49
|
+
return parts
|
|
50
|
+
return result
|
|
51
|
+
|
|
52
|
+
def load_json_from_file(self, json_file: str):
|
|
53
|
+
"""
|
|
54
|
+
Load json from a file
|
|
55
|
+
:param str json_file: string name of a file
|
|
56
|
+
"""
|
|
57
|
+
with open(json_file) as jf:
|
|
58
|
+
parsed_json = json.load(jf)
|
|
59
|
+
self.data = parsed_json
|
|
60
|
+
|
|
61
|
+
def load_fedramp_version_5_mapping(self):
|
|
62
|
+
"""
|
|
63
|
+
Load FedRAMP version 5 mapping
|
|
64
|
+
"""
|
|
65
|
+
from importlib.resources import path as resource_path
|
|
66
|
+
|
|
67
|
+
with resource_path("regscale.integrations.public.fedramp.mappings", "fedramp_r5_parts.json") as json_file_path:
|
|
68
|
+
self.load_json_from_file(json_file_path.__str__())
|
|
69
|
+
|
|
70
|
+
def load_fedramp_version_4_mapping(self):
|
|
71
|
+
"""
|
|
72
|
+
Load FedRAMP version 4 mapping
|
|
73
|
+
"""
|
|
74
|
+
from importlib.resources import path as resource_path
|
|
75
|
+
|
|
76
|
+
with resource_path("regscale.integrations.public.fedramp.mappings", "fedramp_r4_parts.json") as json_file_path:
|
|
77
|
+
self.load_json_from_file(json_file_path.__str__())
|
|
78
|
+
|
|
79
|
+
def find_by_control_id_and_part_letter(self, control_label: str, part: str) -> list:
|
|
80
|
+
"""
|
|
81
|
+
Find a mapping by control label and part letter.
|
|
82
|
+
:param str control_label: The control label.
|
|
83
|
+
:param str part: The part letter.
|
|
84
|
+
:return: A list of mappings.
|
|
85
|
+
:rtype: list
|
|
86
|
+
"""
|
|
87
|
+
result = [
|
|
88
|
+
item.get("OSCAL_PART_IDENTIFIER")
|
|
89
|
+
for item in self.data
|
|
90
|
+
if item.get("CONTROLLABEL") == control_label and item.get("Part") == part
|
|
91
|
+
]
|
|
92
|
+
return result
|
|
93
|
+
|
|
94
|
+
def find_by_oscal_control_id_and_part_letter(self, oscal_control_id: str, part: str) -> list:
|
|
95
|
+
"""
|
|
96
|
+
Find a mapping by OSCAL control ID and part letter.
|
|
97
|
+
:param str oscal_control_id:
|
|
98
|
+
:param str part:
|
|
99
|
+
:return: A list of mappings.
|
|
100
|
+
:rtype: list
|
|
101
|
+
"""
|
|
102
|
+
result = [
|
|
103
|
+
item.get("OSCAL_PART_IDENTIFIER")
|
|
104
|
+
for item in self.data
|
|
105
|
+
if item.get("OSCALCONTROL_ID") == oscal_control_id and item.get("Part") == part
|
|
106
|
+
]
|
|
107
|
+
return result
|
|
File without changes
|