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,733 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Base Synqly Model"""
|
|
4
|
+
import json
|
|
5
|
+
import logging
|
|
6
|
+
import signal
|
|
7
|
+
from abc import ABC
|
|
8
|
+
from typing import Any, Callable, Optional, TypeVar, TYPE_CHECKING, Union
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from synqly.engine.client import SynqlyEngine
|
|
12
|
+
from synqly.management import ProviderConfig
|
|
13
|
+
from synqly.engine.resources import Asset as InventoryAsset, SecurityFinding, Ticket
|
|
14
|
+
|
|
15
|
+
import httpx
|
|
16
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
17
|
+
from rich.progress import Progress
|
|
18
|
+
|
|
19
|
+
from regscale.core.app.api import Api
|
|
20
|
+
from regscale.core.app.application import Application
|
|
21
|
+
from regscale.core.app.utils.app_utils import create_progress_object, error_and_exit
|
|
22
|
+
from regscale.models.integration_models.synqly_models.connector_types import ConnectorType
|
|
23
|
+
from regscale.models.integration_models.synqly_models.ocsf_mapper import Mapper
|
|
24
|
+
from regscale.models.integration_models.synqly_models.param import Param
|
|
25
|
+
from regscale.models.integration_models.synqly_models.tenants import Tenant
|
|
26
|
+
|
|
27
|
+
S = TypeVar("S", bound="SynqlyModel")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class SynqlyModel(BaseModel, ABC):
|
|
31
|
+
"""Class for Synqly integration to add functionality to interact with Synqly via SDK"""
|
|
32
|
+
|
|
33
|
+
model_config = ConfigDict(populate_by_name=True, use_enum_values=True, arbitrary_types_allowed=True)
|
|
34
|
+
|
|
35
|
+
_connector_type: Optional[str] = ""
|
|
36
|
+
tenant: Optional[Tenant] = None
|
|
37
|
+
client: Optional[Any] = None
|
|
38
|
+
connectors: dict = Field(default_factory=dict)
|
|
39
|
+
# defined using the openApi spec on 7/16/2024, this is updated via _get_integrations_and_secrets()
|
|
40
|
+
connector_types: set = Field(default_factory=lambda: set([connector.__str__() for connector in ConnectorType]))
|
|
41
|
+
terminated: Optional[bool] = False
|
|
42
|
+
app: Application = Field(default_factory=Application, alias="app")
|
|
43
|
+
api: Api = Field(default_factory=Api, alias="api")
|
|
44
|
+
logger: logging.Logger = Field(default=logging.getLogger("rich"), alias="logger")
|
|
45
|
+
job_progress: Optional[Progress] = None
|
|
46
|
+
integration: str = ""
|
|
47
|
+
integration_name: str = Field(default="", description="This stores the proper name of the integration for logging.")
|
|
48
|
+
integrations: list = Field(default_factory=list)
|
|
49
|
+
integrations_and_secrets: dict = Field(default_factory=dict)
|
|
50
|
+
integration_config: Any = None
|
|
51
|
+
capabilities: list[str] = Field(default_factory=list)
|
|
52
|
+
auth_object: str = ""
|
|
53
|
+
auth_object_type: str = ""
|
|
54
|
+
config_types: list = Field(default_factory=list)
|
|
55
|
+
mapper: Mapper = Field(default_factory=Mapper, alias="mapper")
|
|
56
|
+
required_secrets: dict[str, list[Param]] = Field(default_factory=dict)
|
|
57
|
+
optional_params: dict[str, list[Param]] = Field(default_factory=dict)
|
|
58
|
+
required_params: dict[str, list[Param]] = Field(default_factory=dict)
|
|
59
|
+
created_integration_objects: list = Field(default_factory=list)
|
|
60
|
+
created_regscale_objects: list = Field(default_factory=list)
|
|
61
|
+
updated_regscale_objects: list = Field(default_factory=list)
|
|
62
|
+
regscale_objects_to_update: list = Field(default_factory=list)
|
|
63
|
+
|
|
64
|
+
def __init__(self: S, connector_type: Optional[str] = None, integration: Optional[str] = None, **kwargs):
|
|
65
|
+
try:
|
|
66
|
+
if connector_type and integration:
|
|
67
|
+
super().__init__(connector_type=connector_type, integration=integration, **kwargs)
|
|
68
|
+
job_progress = create_progress_object()
|
|
69
|
+
self.job_progress = job_progress
|
|
70
|
+
self.logger.info(f"Initializing {connector_type} connector for the {integration} integration...")
|
|
71
|
+
self.integration_name = " ".join(string.title() for string in self.integration.split("_"))
|
|
72
|
+
self._connector_type = connector_type.lower()
|
|
73
|
+
self.connectors = self._get_integrations_and_secrets()
|
|
74
|
+
if self._connector_type not in self.connector_types:
|
|
75
|
+
raise ValueError(
|
|
76
|
+
f"Invalid connector type: {self._connector_type}. "
|
|
77
|
+
f"Please use one of {', '.join(self.connector_types)}."
|
|
78
|
+
)
|
|
79
|
+
self.integrations = self.connectors[self._connector_type]
|
|
80
|
+
self.integration = self._correct_integration_name(integration)
|
|
81
|
+
if self.integration not in self.integrations:
|
|
82
|
+
raise ValueError(
|
|
83
|
+
f"Invalid integration: {self.integration}. Please use one of {', '.join(self.integrations)}."
|
|
84
|
+
)
|
|
85
|
+
# Populate the required secrets and optional params
|
|
86
|
+
self._flatten_secrets()
|
|
87
|
+
# Initialize signal handlers to intercept Ctrl-C and perform cleanup
|
|
88
|
+
signal.signal(signal.SIGINT, lambda sig, frame: self._cleanup_handler())
|
|
89
|
+
signal.signal(signal.SIGTERM, lambda sig, frame: self._cleanup_handler())
|
|
90
|
+
self.logger.info(f"{self._connector_type} connector for {integration} initialized.")
|
|
91
|
+
else:
|
|
92
|
+
# if the connector type and integration are not provided, we need to generate jobs
|
|
93
|
+
super().__init__(**kwargs)
|
|
94
|
+
except Exception as e:
|
|
95
|
+
if connector_type and integration:
|
|
96
|
+
error_and_exit(f"Error creating {connector_type} connector for the {integration} integration: {e}")
|
|
97
|
+
else:
|
|
98
|
+
error_and_exit(f"Error creating {self.__class__.__name__}: {e}")
|
|
99
|
+
|
|
100
|
+
def _correct_integration_name(self, provided_integration: str) -> str:
|
|
101
|
+
"""
|
|
102
|
+
Correct the integration name to match the integration case
|
|
103
|
+
|
|
104
|
+
:param str provided_integration: Integration name to correct
|
|
105
|
+
:return: Corrected integration name
|
|
106
|
+
:rtype: str
|
|
107
|
+
"""
|
|
108
|
+
for integration in self.integrations:
|
|
109
|
+
if provided_integration.lower() == integration.lower():
|
|
110
|
+
return integration
|
|
111
|
+
return provided_integration
|
|
112
|
+
|
|
113
|
+
def _update_secret_and_param(self, key: str, data: dict, attribute: str) -> None:
|
|
114
|
+
"""
|
|
115
|
+
Update the secret and parameter objects
|
|
116
|
+
|
|
117
|
+
:param str key: The key to update
|
|
118
|
+
:param dict data: The data to update
|
|
119
|
+
:param str attribute: The attribute to update
|
|
120
|
+
:rtype: None
|
|
121
|
+
"""
|
|
122
|
+
if key == "credential" and any(isinstance(v, dict) for v in data[key].values()):
|
|
123
|
+
for k, v in data[key].items():
|
|
124
|
+
if k == "optional":
|
|
125
|
+
continue
|
|
126
|
+
getattr(self, attribute)[k] = Param(**v)
|
|
127
|
+
elif key != "optional":
|
|
128
|
+
try:
|
|
129
|
+
getattr(self, attribute)[key] = Param(**data[key])
|
|
130
|
+
except Exception as e:
|
|
131
|
+
self.logger.error(f"Error updating {key} in {attribute}: {e}\n{data=}")
|
|
132
|
+
|
|
133
|
+
def _flatten_secrets(self, integration: Optional[str] = None, return_secrets: bool = False) -> Optional[dict]:
|
|
134
|
+
"""
|
|
135
|
+
Flatten the secrets for the integration into required and optional parameters
|
|
136
|
+
|
|
137
|
+
:param Optional[str] integration: Integration to flatten secrets for, used during code gen, defaults to None
|
|
138
|
+
:param bool return_secrets: Whether the secrets should be returned, used during code gen, defaults to False
|
|
139
|
+
:return: Params and secrets as a dictionary, if return_secrets is True
|
|
140
|
+
:rtype: Optional[dict]
|
|
141
|
+
"""
|
|
142
|
+
if return_secrets:
|
|
143
|
+
self.required_secrets = {}
|
|
144
|
+
self.optional_params = {}
|
|
145
|
+
self.required_params = {}
|
|
146
|
+
for attribute in ["required_secrets", "optional_params", "required_params"]:
|
|
147
|
+
key = f"{self._connector_type}_{self.integration}" if not integration else f"{integration}"
|
|
148
|
+
data = self.integrations_and_secrets[key][attribute]
|
|
149
|
+
for secret in data:
|
|
150
|
+
self._update_secret_and_param(secret, data, attribute)
|
|
151
|
+
if return_secrets:
|
|
152
|
+
return {
|
|
153
|
+
"required_secrets": self.required_secrets,
|
|
154
|
+
"optional_params": self.optional_params,
|
|
155
|
+
"expected_params": self.required_params,
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
def create_config_and_validate_secrets(self, **kwargs) -> Any:
|
|
159
|
+
"""
|
|
160
|
+
Create a new config for the integration and validates the secrets using kwargs and init.yaml
|
|
161
|
+
|
|
162
|
+
:raises ModuleNotFoundError: If unable to parse the config object
|
|
163
|
+
:return: The ticketing configuration
|
|
164
|
+
:rtype: Any
|
|
165
|
+
"""
|
|
166
|
+
from synqly import management as mgmt
|
|
167
|
+
|
|
168
|
+
# store config if we need to update it, if needed
|
|
169
|
+
config = self.app.config
|
|
170
|
+
# build a dictionary to contain missing secrets
|
|
171
|
+
missing_secrets: dict[str, Param] = {}
|
|
172
|
+
skip_prompts = kwargs.pop("skip_prompts", False)
|
|
173
|
+
config_object_name = "".join([x.title() for x in self.integration.split("_")])
|
|
174
|
+
config_object = getattr(mgmt, f"ProviderConfig_{self._connector_type.title()}{config_object_name}", None)
|
|
175
|
+
if not config_object:
|
|
176
|
+
raise ModuleNotFoundError(f"Unable to find the config object for {self._connector_type}_{self.integration}")
|
|
177
|
+
check_attributes = [self.required_secrets, self.required_params]
|
|
178
|
+
for attribute in check_attributes:
|
|
179
|
+
for secret in attribute:
|
|
180
|
+
if secret != "optional":
|
|
181
|
+
kwargs.update(
|
|
182
|
+
self._update_config_and_kwargs(
|
|
183
|
+
attribute=attribute, # type: ignore
|
|
184
|
+
key=secret,
|
|
185
|
+
config=config,
|
|
186
|
+
skip_prompts=skip_prompts,
|
|
187
|
+
missing_secrets=missing_secrets,
|
|
188
|
+
**kwargs,
|
|
189
|
+
)
|
|
190
|
+
)
|
|
191
|
+
self.app.save_config(config)
|
|
192
|
+
if missing_secrets:
|
|
193
|
+
self.logger.error("Missing required secrets:")
|
|
194
|
+
for secret, data in missing_secrets.items():
|
|
195
|
+
self.logger.error(f"{secret} ({data.expected_type}) - {data.description}")
|
|
196
|
+
error_and_exit("Please provide the required secrets mentioned above.")
|
|
197
|
+
self.integration_config = config_object(
|
|
198
|
+
type=f"{self._connector_type.lower()}_{self.integration.lower()}",
|
|
199
|
+
url=kwargs.get("url"),
|
|
200
|
+
credential=self._get_auth_method(**kwargs),
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
def _get_auth_method(self, **kwargs) -> Any:
|
|
204
|
+
"""
|
|
205
|
+
Get the authentication method for the integration
|
|
206
|
+
|
|
207
|
+
:raises ValueError: If unable to find the authentication method
|
|
208
|
+
:return: The authentication method
|
|
209
|
+
:rtype: Any
|
|
210
|
+
"""
|
|
211
|
+
from synqly import management as mgmt
|
|
212
|
+
|
|
213
|
+
if auth_object := getattr(mgmt, self.auth_object):
|
|
214
|
+
return auth_object(type=self.auth_object_type, **kwargs)
|
|
215
|
+
else:
|
|
216
|
+
raise ValueError(f"Unable to find the authentication method for {self.integration}")
|
|
217
|
+
|
|
218
|
+
def _update_config_and_kwargs(
|
|
219
|
+
self,
|
|
220
|
+
attribute: dict[str, Param],
|
|
221
|
+
key: str,
|
|
222
|
+
config: Any,
|
|
223
|
+
skip_prompts: bool,
|
|
224
|
+
missing_secrets: dict[str, Param],
|
|
225
|
+
**kwargs,
|
|
226
|
+
) -> dict:
|
|
227
|
+
"""
|
|
228
|
+
Update the config object and keyword arguments
|
|
229
|
+
|
|
230
|
+
:param dict[str, list[Param]] attribute: The attribute to update
|
|
231
|
+
:param str key: The secret to check and update
|
|
232
|
+
:param Any config: The config object to update
|
|
233
|
+
:param bool skip_prompts: Flag to indicate if prompts should be skipped
|
|
234
|
+
:param dict[str, Param] missing_secrets: Dictionary to store missing secrets
|
|
235
|
+
:return: Updated kwargs if the secret is found in the config, rather than kwargs
|
|
236
|
+
:rtype: dict
|
|
237
|
+
"""
|
|
238
|
+
if key not in kwargs and not config.get(f"{self._connector_type}_{self.integration}_{key}"):
|
|
239
|
+
if not skip_prompts:
|
|
240
|
+
self.logger.info(f"Enter the {key} for {self.integration}. Description: {attribute[key].description}")
|
|
241
|
+
provided_secret = input(f"{key}: ")
|
|
242
|
+
kwargs[key] = provided_secret
|
|
243
|
+
config[f"{self._connector_type}_{self.integration}_{key}"] = provided_secret
|
|
244
|
+
else:
|
|
245
|
+
missing_secrets[key] = attribute[key]
|
|
246
|
+
# make sure the secret is in the config
|
|
247
|
+
if key in kwargs and not config.get(f"{self._connector_type}_{self.integration}_{key}"):
|
|
248
|
+
config[f"{self._connector_type}_{self.integration}_{key}"] = kwargs[key]
|
|
249
|
+
# make sure the secret is in the kwargs, load it from the config
|
|
250
|
+
if key not in kwargs and config.get(f"{self._connector_type}_{self.integration}_{key}"):
|
|
251
|
+
kwargs[key] = config[f"{self._connector_type}_{self.integration}_{key}"]
|
|
252
|
+
return kwargs
|
|
253
|
+
|
|
254
|
+
@staticmethod
|
|
255
|
+
def _load_from_package() -> dict:
|
|
256
|
+
"""
|
|
257
|
+
Load the capabilities.json from the RegScale CLI package
|
|
258
|
+
|
|
259
|
+
:return: The capabilities.json
|
|
260
|
+
:rtype: dict
|
|
261
|
+
"""
|
|
262
|
+
import importlib.resources as pkg_resources
|
|
263
|
+
|
|
264
|
+
# check if the filepath exists before trying to open it
|
|
265
|
+
with pkg_resources.open_text("regscale.models.integration_models.synqly_models", "capabilities.json") as file:
|
|
266
|
+
data = json.load(file)
|
|
267
|
+
return data["result"]
|
|
268
|
+
|
|
269
|
+
def _get_integrations_and_secrets(self, return_params: bool = False) -> dict:
|
|
270
|
+
"""
|
|
271
|
+
Function to get the integrations and secrets from the API
|
|
272
|
+
|
|
273
|
+
:param bool return_params: Flag to indicate if the params should be returned
|
|
274
|
+
:return: Integrations and secrets
|
|
275
|
+
:rtype: dict
|
|
276
|
+
"""
|
|
277
|
+
raw_data = self._load_from_package()
|
|
278
|
+
return self._parse_api_spec_data(raw_data, return_params)
|
|
279
|
+
|
|
280
|
+
def _parse_api_spec_data(self, data: dict, return_params: bool = False) -> dict:
|
|
281
|
+
"""
|
|
282
|
+
Function to parse the Synqly OpenAPI spec metadata
|
|
283
|
+
|
|
284
|
+
:param dict data: Data to parse
|
|
285
|
+
:param bool return_params: Flag to indicate if the params should be returned
|
|
286
|
+
:return: Parsed integrations, or parsed data and params if return_params is True
|
|
287
|
+
:rtype: dict
|
|
288
|
+
"""
|
|
289
|
+
integrations: dict = {}
|
|
290
|
+
# per Synqly, this is the best way to get all integrations in one place
|
|
291
|
+
parsed_integrations = [integration["id"] for integration in data if "mock" not in integration.get("id", "")]
|
|
292
|
+
self.connector_types = {key.split("_")[0] for key in parsed_integrations}
|
|
293
|
+
total_count = len(parsed_integrations)
|
|
294
|
+
scrubbed_data = {
|
|
295
|
+
integration["id"]: integration for integration in data if integration["id"] in parsed_integrations
|
|
296
|
+
}
|
|
297
|
+
parsed_count = 0
|
|
298
|
+
# check if we are initializing for a specific integration and skip processing the rest of the integrations
|
|
299
|
+
if scrubbed_data.get(f"{self._connector_type}_{self.integration}"):
|
|
300
|
+
key = f"{self._connector_type}_{self.integration}"
|
|
301
|
+
self._build_integration_and_secrets(
|
|
302
|
+
integrations=integrations,
|
|
303
|
+
key=key,
|
|
304
|
+
data=scrubbed_data,
|
|
305
|
+
parsed_count=parsed_count,
|
|
306
|
+
total_count=1,
|
|
307
|
+
)
|
|
308
|
+
else:
|
|
309
|
+
for key in scrubbed_data.keys():
|
|
310
|
+
code_gen = False
|
|
311
|
+
# Split the string at the underscore
|
|
312
|
+
if self._connector_type and self._connector_type.lower() not in key.lower():
|
|
313
|
+
continue
|
|
314
|
+
elif not self._connector_type:
|
|
315
|
+
code_gen = True
|
|
316
|
+
self._build_integration_and_secrets(
|
|
317
|
+
integrations=integrations,
|
|
318
|
+
key=key,
|
|
319
|
+
data=scrubbed_data,
|
|
320
|
+
parsed_count=parsed_count,
|
|
321
|
+
total_count=total_count,
|
|
322
|
+
)
|
|
323
|
+
if code_gen:
|
|
324
|
+
self._connector_type = None
|
|
325
|
+
if return_params:
|
|
326
|
+
return self.integrations_and_secrets
|
|
327
|
+
return integrations
|
|
328
|
+
|
|
329
|
+
def _build_integration_and_secrets(
|
|
330
|
+
self, integrations: dict, key: str, data: dict, parsed_count: int, total_count: int
|
|
331
|
+
) -> None:
|
|
332
|
+
"""
|
|
333
|
+
Function to build the integration and secrets
|
|
334
|
+
|
|
335
|
+
:param dict integrations: Integrations dictionary that will be updated
|
|
336
|
+
:param str key: Integration key in the data dictionary
|
|
337
|
+
:param dict data: Data containing all integrations and their config
|
|
338
|
+
:param int parsed_count: Parsed count, used for logging
|
|
339
|
+
:param int total_count: Total count, used for logging
|
|
340
|
+
"""
|
|
341
|
+
self.config_types.append(key)
|
|
342
|
+
self.logger.debug(f"Processing secrets for {key}")
|
|
343
|
+
self.integrations_and_secrets[key] = self._parse_capabilities_params_and_secrets(data, key)
|
|
344
|
+
self.logger.debug(f"Successfully processed secrets for {key}")
|
|
345
|
+
parsed_count += 1
|
|
346
|
+
# Add the item to the dictionary
|
|
347
|
+
if self._connector_type not in integrations:
|
|
348
|
+
integrations[self._connector_type] = []
|
|
349
|
+
integrations[self._connector_type].append(key.replace(f"{self._connector_type}_", ""))
|
|
350
|
+
self.logger.debug(f"Successfully processed {parsed_count}/{total_count} integrations")
|
|
351
|
+
|
|
352
|
+
def _set_optional_flag(self, data: dict, optional: bool) -> dict:
|
|
353
|
+
"""
|
|
354
|
+
Function to recursively set the optional flag in the provided dictionary
|
|
355
|
+
|
|
356
|
+
:param dict data: Object to set the optional flag for
|
|
357
|
+
:param bool optional: Flag to indicate if the object is optional
|
|
358
|
+
:return: Updated dictionary object with the optional flag set
|
|
359
|
+
:rtype: dict
|
|
360
|
+
"""
|
|
361
|
+
# Check if there is a nested dict and set the optional flag for the nested dict
|
|
362
|
+
for key, value in data.items():
|
|
363
|
+
if isinstance(value, dict):
|
|
364
|
+
data[key]["optional"] = data[key].get("optional", optional)
|
|
365
|
+
elif isinstance(value, list):
|
|
366
|
+
for item in value:
|
|
367
|
+
if isinstance(item, dict):
|
|
368
|
+
index = value.index(item)
|
|
369
|
+
value[index] = self._set_optional_flag(item, optional) # type: ignore
|
|
370
|
+
data["optional"] = optional
|
|
371
|
+
return data
|
|
372
|
+
|
|
373
|
+
@staticmethod
|
|
374
|
+
def _select_auth_method(credential_schema: dict) -> dict:
|
|
375
|
+
"""
|
|
376
|
+
Function to iterate different auth methods and their required secrets: basic -> token -> oath
|
|
377
|
+
|
|
378
|
+
:param dict credential_schema: Credential schema to select auth method from
|
|
379
|
+
:return: Auth method schema
|
|
380
|
+
:rtype: dict
|
|
381
|
+
"""
|
|
382
|
+
for item in credential_schema["oneOf"]:
|
|
383
|
+
if "basic" in item.get("title", "").lower():
|
|
384
|
+
return item
|
|
385
|
+
for item in credential_schema["oneOf"]:
|
|
386
|
+
if "token" in item.get("title", "").lower():
|
|
387
|
+
return item
|
|
388
|
+
for item in credential_schema["oneOf"]:
|
|
389
|
+
if "oath" in item.get("title", "").lower():
|
|
390
|
+
return item
|
|
391
|
+
|
|
392
|
+
def _process_credential_schema(
|
|
393
|
+
self, credential_schema: dict, credentials: dict, integration: Optional[str] = None
|
|
394
|
+
) -> None:
|
|
395
|
+
"""
|
|
396
|
+
Function to process credential schema and update credentials dictionary
|
|
397
|
+
|
|
398
|
+
:param dict credential_schema: Credential schema to update credentials from
|
|
399
|
+
:param dict credentials: Credentials to update
|
|
400
|
+
:param Optional[str] integration: Integration to set authentication type for, defaults to None
|
|
401
|
+
:rtype: None
|
|
402
|
+
"""
|
|
403
|
+
if "oneOf" in credential_schema:
|
|
404
|
+
credential_schema = self._select_auth_method(credential_schema) or {}
|
|
405
|
+
required_secrets = credential_schema.get("required", [])
|
|
406
|
+
for key, value in credential_schema.get("properties", {}).items():
|
|
407
|
+
if key == "type" and integration and self.integration.lower() == integration.lower():
|
|
408
|
+
authentication_object = f'{credential_schema["x-synqly-credential"]["type"]}_'
|
|
409
|
+
authentication_object += "OAuthClient" if "o_auth" in value["const"] else value["const"].title()
|
|
410
|
+
self.auth_object = authentication_object
|
|
411
|
+
self.auth_object_type = value["const"]
|
|
412
|
+
elif key != "type" and value.get("nullable", False):
|
|
413
|
+
continue
|
|
414
|
+
elif key != "type":
|
|
415
|
+
value["optional"] = value in required_secrets
|
|
416
|
+
credentials[key] = value
|
|
417
|
+
|
|
418
|
+
def _parse_capabilities_params_and_secrets(self, data: dict, key: Optional[str] = None) -> dict:
|
|
419
|
+
"""
|
|
420
|
+
Function to parse the required secrets, params and capabilities from the Synqly metadata, with the provided key
|
|
421
|
+
|
|
422
|
+
:param dict data: Data from the OpenAPI spec
|
|
423
|
+
:param Optional[str] key: The schema key to parse the required secrets from, defaults to None
|
|
424
|
+
:raises KeyError: If the schema key is not found in the OpenAPI schema
|
|
425
|
+
:raises ValueError: If no 'credential' property is found for the schema key
|
|
426
|
+
:return: Dictionary of the required secrets
|
|
427
|
+
:rtype: dict
|
|
428
|
+
"""
|
|
429
|
+
if key is None:
|
|
430
|
+
raise KeyError(f"Key '{key}' not found in the JSON schema.")
|
|
431
|
+
|
|
432
|
+
schema = data[key]
|
|
433
|
+
if schema.get("provider_config") is None:
|
|
434
|
+
raise ValueError(f"No 'provider_config' found for key '{key}'.")
|
|
435
|
+
|
|
436
|
+
if schema["provider_config"].get("properties") is None:
|
|
437
|
+
raise ValueError(f"No 'properties' found in the 'provider_config' for key '{key}'.")
|
|
438
|
+
|
|
439
|
+
operations = schema.get("operations", [])
|
|
440
|
+
capabilities = [item["name"] for item in operations if item.get("supported")]
|
|
441
|
+
capabilities_params = list(
|
|
442
|
+
field
|
|
443
|
+
for item in operations
|
|
444
|
+
if item.get("supported") and "required_fields" in item.keys()
|
|
445
|
+
for field in item.get("required_fields", [])
|
|
446
|
+
)
|
|
447
|
+
if self.integration.lower() in key.lower():
|
|
448
|
+
self.capabilities = capabilities
|
|
449
|
+
schema = schema["provider_config"]
|
|
450
|
+
|
|
451
|
+
credentials: dict = {}
|
|
452
|
+
final_creds: dict = {"description": "", "required_params": {}, "optional_params": {}, "required_secrets": {}}
|
|
453
|
+
required: list[str] = schema.get("required", [])
|
|
454
|
+
|
|
455
|
+
for prop_key, prop in schema["properties"].items():
|
|
456
|
+
if prop_key == "type":
|
|
457
|
+
continue
|
|
458
|
+
elif prop_key == "credential":
|
|
459
|
+
# we must remove the connector type from the key by finding the first _ and using the rest of the string
|
|
460
|
+
self._process_credential_schema(
|
|
461
|
+
credential_schema=prop, credentials=credentials, integration=key[key.find("_") + 1 :]
|
|
462
|
+
)
|
|
463
|
+
elif prop_key in required:
|
|
464
|
+
prop["optional"] = False
|
|
465
|
+
credentials[prop_key] = prop
|
|
466
|
+
else:
|
|
467
|
+
if prop.get("nullable", True):
|
|
468
|
+
prop["optional"] = True
|
|
469
|
+
final_creds["optional_params"][prop_key] = prop
|
|
470
|
+
else:
|
|
471
|
+
prop["optional"] = False
|
|
472
|
+
final_creds["required_params"][prop_key] = prop
|
|
473
|
+
self._parse_capability_params(capabilities_params=capabilities_params, integration=key, final_creds=final_creds)
|
|
474
|
+
|
|
475
|
+
final_creds["required_secrets"] = credentials
|
|
476
|
+
final_creds["description"] = schema.get("description", "")
|
|
477
|
+
final_creds["capabilities"] = capabilities
|
|
478
|
+
return {**data[key], **final_creds}
|
|
479
|
+
|
|
480
|
+
@staticmethod
|
|
481
|
+
def _parse_capability_params(capabilities_params: list[str], integration: str, final_creds: dict) -> None:
|
|
482
|
+
"""
|
|
483
|
+
Function to parse the capability parameters and determine if they are required or optional
|
|
484
|
+
|
|
485
|
+
:param list[str] capabilities_params: List of capability parameters
|
|
486
|
+
:param str integration: The name of the integration
|
|
487
|
+
:param dict final_creds: The final credentials dictionary to update
|
|
488
|
+
:rtype: None
|
|
489
|
+
"""
|
|
490
|
+
for param in capabilities_params:
|
|
491
|
+
if param in ["summary", "creator", "priority", "status"]:
|
|
492
|
+
continue
|
|
493
|
+
prop = {
|
|
494
|
+
"description": f'{integration[integration.find("_") + 1:]} {" ".join(param.split("_"))}',
|
|
495
|
+
"type": "string",
|
|
496
|
+
"optional": False,
|
|
497
|
+
}
|
|
498
|
+
final_creds["required_params"][param] = prop
|
|
499
|
+
|
|
500
|
+
@staticmethod
|
|
501
|
+
def create_name_safe_string(tenant_name: str, replace_char: Optional[str] = "-") -> str:
|
|
502
|
+
"""
|
|
503
|
+
Function to create a friendly Synqly tenant name
|
|
504
|
+
|
|
505
|
+
:param str tenant_name: The original string to convert
|
|
506
|
+
:param Optional[str] replace_char: The character to replace unsafe characters with, defaults to "-"
|
|
507
|
+
:return: Safe tenant name
|
|
508
|
+
:rtype: str
|
|
509
|
+
"""
|
|
510
|
+
for unsafe_char in [".", " ", "/", ":"]:
|
|
511
|
+
tenant_name = tenant_name.replace(unsafe_char, replace_char)
|
|
512
|
+
return tenant_name.lower()
|
|
513
|
+
|
|
514
|
+
def _cleanup_handler(self):
|
|
515
|
+
"""
|
|
516
|
+
Deletes resources created by the connector and integration
|
|
517
|
+
"""
|
|
518
|
+
if self.tenant:
|
|
519
|
+
self.logger.info(f"\nCleaning up {self._connector_type} connector resources...")
|
|
520
|
+
self.tenant.management_client.accounts.delete(self.tenant.account_id)
|
|
521
|
+
self.logger.debug("Cleaned up Account " + self.tenant.account_id)
|
|
522
|
+
self.terminated = True
|
|
523
|
+
self.logger.info("Cleanup complete.")
|
|
524
|
+
|
|
525
|
+
def get_or_create_tenant(self, synqly_org_token: str, new_tenant_name: str):
|
|
526
|
+
"""
|
|
527
|
+
Adds a new "tenant" to the App. A tenant represents a user or
|
|
528
|
+
organization within your application.
|
|
529
|
+
|
|
530
|
+
:param str synqly_org_token: The Synqly Organization token
|
|
531
|
+
:param str new_tenant_name: The name of the new tenant
|
|
532
|
+
:raises ValueError: If the tenant already exists
|
|
533
|
+
"""
|
|
534
|
+
from synqly import management as mgmt
|
|
535
|
+
from synqly.management.client import SynqlyManagement
|
|
536
|
+
|
|
537
|
+
# configure a custom httpx_client so that all errors are retried
|
|
538
|
+
transport = httpx.HTTPTransport(retries=3)
|
|
539
|
+
|
|
540
|
+
# this creates a httpx logger
|
|
541
|
+
management_client = SynqlyManagement(
|
|
542
|
+
token=synqly_org_token,
|
|
543
|
+
httpx_client=httpx.Client(transport=transport),
|
|
544
|
+
)
|
|
545
|
+
# Get the httpx logger and set the logging level to CRITICAL in order to suppress all lower level log messages
|
|
546
|
+
httpx_logger = logging.getLogger("httpx")
|
|
547
|
+
httpx_logger.setLevel(logging.CRITICAL)
|
|
548
|
+
|
|
549
|
+
# Each tenant needs an associated Account in Synqly, so we create that now.
|
|
550
|
+
|
|
551
|
+
account_request = mgmt.CreateAccountRequest(name=new_tenant_name)
|
|
552
|
+
try:
|
|
553
|
+
account_response = management_client.accounts.create(request=account_request)
|
|
554
|
+
account_id = account_response.result.account.id
|
|
555
|
+
except Exception as ex:
|
|
556
|
+
existing_accounts = management_client.accounts.list(filter=f"name[eq]{new_tenant_name}")
|
|
557
|
+
account_id = [account.id for account in existing_accounts.result if account.fullname == new_tenant_name]
|
|
558
|
+
if not account_id:
|
|
559
|
+
raise ValueError("Failed to create account: " + str(ex))
|
|
560
|
+
account_id = account_id[0] # type: ignore
|
|
561
|
+
|
|
562
|
+
self.tenant = Tenant(
|
|
563
|
+
tenant_name=new_tenant_name,
|
|
564
|
+
account_id=account_id,
|
|
565
|
+
management_client=management_client,
|
|
566
|
+
engine_client=None,
|
|
567
|
+
)
|
|
568
|
+
|
|
569
|
+
def configure_integration(self, tenant_name: str, provider_config: "ProviderConfig", retries: int = 3):
|
|
570
|
+
"""
|
|
571
|
+
Configures a Synqly Integration for a tenant
|
|
572
|
+
|
|
573
|
+
:param str tenant_name: The name of the tenant
|
|
574
|
+
:param ProviderConfig provider_config: The provider configuration
|
|
575
|
+
:param int retries: The number of retries to attempt to create or recreate the integration
|
|
576
|
+
:raises RuntimeError: If unable to create the Integration after 3 attempts
|
|
577
|
+
:raises ValueError: If unable to find an existing Integration for the provided tenant_name
|
|
578
|
+
"""
|
|
579
|
+
from synqly import management as mgmt
|
|
580
|
+
from synqly.engine.client import SynqlyEngine
|
|
581
|
+
|
|
582
|
+
# check retries
|
|
583
|
+
if retries == 0:
|
|
584
|
+
raise RuntimeError("Failed to create Integration after 3 attempts")
|
|
585
|
+
# Use the Management API to create a Synqly Integration
|
|
586
|
+
integration_name = f"{tenant_name}-integration"
|
|
587
|
+
integration_req = mgmt.CreateIntegrationRequest(
|
|
588
|
+
name=integration_name,
|
|
589
|
+
provider_config=provider_config,
|
|
590
|
+
)
|
|
591
|
+
# try to create it, if there is an error see if it already exists
|
|
592
|
+
try:
|
|
593
|
+
integration_resp = self.tenant.management_client.integrations.create(
|
|
594
|
+
account_id=self.tenant.account_id, request=integration_req
|
|
595
|
+
)
|
|
596
|
+
# Add Synqly Engine client to the Tenant for use in the background job
|
|
597
|
+
self.tenant.engine_client = SynqlyEngine(
|
|
598
|
+
token=integration_resp.result.token.access.secret,
|
|
599
|
+
)
|
|
600
|
+
self.client = self.tenant.engine_client
|
|
601
|
+
self.logger.debug(
|
|
602
|
+
"Created {} Integration '{}' for {}".format(
|
|
603
|
+
integration_resp.result.integration.category,
|
|
604
|
+
integration_resp.result.integration.id,
|
|
605
|
+
tenant_name,
|
|
606
|
+
)
|
|
607
|
+
)
|
|
608
|
+
except Exception as ex:
|
|
609
|
+
existing_integrations = self.tenant.management_client.integrations.list(
|
|
610
|
+
filter=f"name[eq]{integration_name}"
|
|
611
|
+
)
|
|
612
|
+
integrations = [
|
|
613
|
+
integration for integration in existing_integrations.result if integration.name == integration_name
|
|
614
|
+
]
|
|
615
|
+
if not integrations:
|
|
616
|
+
raise ValueError(f"Failed to create Integration. {ex}")
|
|
617
|
+
for integration in integrations:
|
|
618
|
+
self.logger.debug(
|
|
619
|
+
"Deleting existing %s Integration '%s' for %s.",
|
|
620
|
+
integration.category,
|
|
621
|
+
integration.id,
|
|
622
|
+
tenant_name,
|
|
623
|
+
)
|
|
624
|
+
self.tenant.management_client.integrations.delete(
|
|
625
|
+
account_id=self.tenant.account_id, integration_id=integration.id
|
|
626
|
+
)
|
|
627
|
+
self.logger.debug("Retrying to create Integration, remaining attempts: %i...", retries)
|
|
628
|
+
self.configure_integration(tenant_name, provider_config, retries - 1)
|
|
629
|
+
|
|
630
|
+
def integration_sync(self, *args, **kwargs):
|
|
631
|
+
"""
|
|
632
|
+
Method to run the integration sync process
|
|
633
|
+
"""
|
|
634
|
+
pass
|
|
635
|
+
|
|
636
|
+
def fetch_integration_data(
|
|
637
|
+
self, func: Callable, **kwargs
|
|
638
|
+
) -> list[Union["InventoryAsset", "SecurityFinding", "Ticket"]]:
|
|
639
|
+
"""
|
|
640
|
+
Fetches data from the integration using the provided function and handles pagination
|
|
641
|
+
|
|
642
|
+
:param Callable func: The function to fetch data from the integration
|
|
643
|
+
:return: The data from the integration
|
|
644
|
+
:rtype: list[Union[InventoryAsset, SecurityFinding, Ticket]]
|
|
645
|
+
"""
|
|
646
|
+
query_filter = kwargs.get("filter")
|
|
647
|
+
limit = kwargs.get("limit", 200)
|
|
648
|
+
integration_data: list = []
|
|
649
|
+
fetch_res = func(
|
|
650
|
+
filter=query_filter,
|
|
651
|
+
limit=limit,
|
|
652
|
+
)
|
|
653
|
+
self.logger.info(f"Received {len(fetch_res.result)} record(s) from {self.integration_name}.")
|
|
654
|
+
integration_data.extend(fetch_res.result)
|
|
655
|
+
# check and handle pagination
|
|
656
|
+
if fetch_res.cursor:
|
|
657
|
+
try:
|
|
658
|
+
# fetch.cursor can be an int as a string, or a continuation token
|
|
659
|
+
while int(fetch_res.cursor) == len(integration_data):
|
|
660
|
+
fetch_res = func(
|
|
661
|
+
filter=query_filter,
|
|
662
|
+
limit=limit,
|
|
663
|
+
cursor=fetch_res.cursor,
|
|
664
|
+
)
|
|
665
|
+
integration_data.extend(fetch_res.result)
|
|
666
|
+
except ValueError:
|
|
667
|
+
while fetch_res.cursor:
|
|
668
|
+
fetch_res = func(
|
|
669
|
+
filter=query_filter,
|
|
670
|
+
limit=limit,
|
|
671
|
+
cursor=fetch_res.cursor,
|
|
672
|
+
)
|
|
673
|
+
integration_data.extend(fetch_res.result)
|
|
674
|
+
self.logger.info(f"Received {len(integration_data)} record(s) from {self.integration_name}...")
|
|
675
|
+
self.logger.info(f"Fetched {len(integration_data)} total record(s) from {self.integration_name}...")
|
|
676
|
+
return integration_data
|
|
677
|
+
|
|
678
|
+
def run_integration_sync(self, *args, **kwargs) -> None:
|
|
679
|
+
"""
|
|
680
|
+
Runs the sync process for the integration
|
|
681
|
+
|
|
682
|
+
:param dict kwargs: The keyword arguments to pass to the main function
|
|
683
|
+
:raises Exception: If an error occurs during the sync process, but will clean up
|
|
684
|
+
any resources created by the connector and integration
|
|
685
|
+
:rtype: None
|
|
686
|
+
"""
|
|
687
|
+
import os
|
|
688
|
+
from regscale import __version__
|
|
689
|
+
|
|
690
|
+
synqly_access_token = os.getenv("SYNQLY_ACCESS_TOKEN") or self.app.config.get("synqlyAccessToken")
|
|
691
|
+
if not synqly_access_token or synqly_access_token == self.app.template.get("synqlyAccessToken"):
|
|
692
|
+
error_and_exit(
|
|
693
|
+
"SYNQLY_ACCESS_TOKEN environment variable and synqlyAccessToken in init.yaml is not set or empty "
|
|
694
|
+
"and is required. Please set it and try again."
|
|
695
|
+
)
|
|
696
|
+
self.create_config_and_validate_secrets(**kwargs)
|
|
697
|
+
|
|
698
|
+
from regscale.validation.record import validate_regscale_object
|
|
699
|
+
from urllib.parse import urlparse
|
|
700
|
+
|
|
701
|
+
regscale_id = kwargs.get("regscale_id") or kwargs.get("regscale_ssp_id") or 0
|
|
702
|
+
regscale_module = "securityplans" if kwargs.get("regscale_ssp_id") else kwargs.get("regscale_module")
|
|
703
|
+
|
|
704
|
+
if not validate_regscale_object(parent_id=regscale_id, parent_module=regscale_module):
|
|
705
|
+
error_and_exit(f"RegScale {regscale_module} ID #{regscale_id} does not exist.")
|
|
706
|
+
|
|
707
|
+
domain_name = self.create_name_safe_string(urlparse(self.app.config.get("domain")).netloc)
|
|
708
|
+
tenant_name = (
|
|
709
|
+
f"regscale-cliv{self.create_name_safe_string(__version__, '-')}-{domain_name}-{self.integration.lower()}"
|
|
710
|
+
)
|
|
711
|
+
try:
|
|
712
|
+
self.get_or_create_tenant(synqly_access_token, tenant_name)
|
|
713
|
+
self.logger.debug(f"{tenant_name} tenant created")
|
|
714
|
+
except Exception as e:
|
|
715
|
+
self.logger.error(f"Error creating Tenant {tenant_name}:" + str(e))
|
|
716
|
+
self._cleanup_handler()
|
|
717
|
+
raise e
|
|
718
|
+
|
|
719
|
+
try:
|
|
720
|
+
self.configure_integration(tenant_name, self.integration_config)
|
|
721
|
+
except Exception as e:
|
|
722
|
+
self.logger.error(f"Error configuring provider integration for Tenant {tenant_name}: " + str(e))
|
|
723
|
+
self._cleanup_handler()
|
|
724
|
+
raise e
|
|
725
|
+
|
|
726
|
+
try:
|
|
727
|
+
self.integration_sync(*args, **kwargs)
|
|
728
|
+
except Exception as e:
|
|
729
|
+
self.logger.error("Error running sync job: " + str(e))
|
|
730
|
+
self._cleanup_handler()
|
|
731
|
+
raise e
|
|
732
|
+
finally:
|
|
733
|
+
self._cleanup_handler()
|