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,798 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""RegScale Okta integration"""
|
|
4
|
+
|
|
5
|
+
# standard python imports
|
|
6
|
+
import json
|
|
7
|
+
import time
|
|
8
|
+
from datetime import datetime, timedelta
|
|
9
|
+
from json import JSONDecodeError
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Tuple
|
|
12
|
+
|
|
13
|
+
import click
|
|
14
|
+
import jwcrypto.jwk as jwk
|
|
15
|
+
import python_jwt
|
|
16
|
+
|
|
17
|
+
from regscale.core.app.api import Api
|
|
18
|
+
from regscale.core.app.application import Application
|
|
19
|
+
from regscale.core.app.internal.login import is_valid
|
|
20
|
+
from regscale.core.app.logz import create_logger
|
|
21
|
+
from regscale.core.app.utils.app_utils import (
|
|
22
|
+
check_file_path,
|
|
23
|
+
check_license,
|
|
24
|
+
create_progress_object,
|
|
25
|
+
error_and_exit,
|
|
26
|
+
get_current_datetime,
|
|
27
|
+
parse_url_for_pagination,
|
|
28
|
+
remove_nested_dict,
|
|
29
|
+
save_data_to,
|
|
30
|
+
)
|
|
31
|
+
from regscale.utils.threading.threadhandler import create_threads, thread_assignment
|
|
32
|
+
from regscale.models.app_models.click import file_types, save_output_to
|
|
33
|
+
|
|
34
|
+
LOGIN_ERROR = "Login Error: Invalid RegScale credentials. Please log in for a new token."
|
|
35
|
+
job_progress = create_progress_object()
|
|
36
|
+
logger = create_logger()
|
|
37
|
+
admin_users = []
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
#####################################################################################################
|
|
41
|
+
#
|
|
42
|
+
# Okta Core API Documentation: https://developer.okta.com/docs/reference/core-okta-api/
|
|
43
|
+
# Okta API Postman Collections: https://developer.okta.com/docs/reference/postman-collections/
|
|
44
|
+
#
|
|
45
|
+
#####################################################################################################
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@click.group()
|
|
49
|
+
def okta():
|
|
50
|
+
"""Okta integration to pull Okta users via API."""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@okta.command()
|
|
54
|
+
@click.option(
|
|
55
|
+
"--type",
|
|
56
|
+
type=click.Choice(["SSWS", "Bearer"], case_sensitive=False),
|
|
57
|
+
help="The type of authentication method to use for Okta API.",
|
|
58
|
+
prompt="Choose SSWS or Bearer",
|
|
59
|
+
required=True,
|
|
60
|
+
)
|
|
61
|
+
def authenticate(type: str):
|
|
62
|
+
"""
|
|
63
|
+
Authenticate with Okta API by choosing SSWS or Bearer. SSWS is a security token created
|
|
64
|
+
within Okta admin portal and Bearer token needs a private JWK from Okta Admin portal.
|
|
65
|
+
"""
|
|
66
|
+
app = check_license()
|
|
67
|
+
api = Api()
|
|
68
|
+
authenticate_with_okta(app, api, type)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@okta.command(name="get_active_users")
|
|
72
|
+
@save_output_to()
|
|
73
|
+
@file_types([".csv", ".xlsx"])
|
|
74
|
+
def get_active_users(save_output_to: Path, file_type: str):
|
|
75
|
+
"""
|
|
76
|
+
Get active users from Okta API and save them to a .csv or .xlsx file.
|
|
77
|
+
"""
|
|
78
|
+
save_active_users_from_okta(save_output_to=save_output_to, file_type=file_type)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@okta.command(name="get_inactive_users")
|
|
82
|
+
@click.option(
|
|
83
|
+
"--days",
|
|
84
|
+
type=click.INT,
|
|
85
|
+
help="The number of days to see if a user hasn't logged in since, default is 30.",
|
|
86
|
+
default=30,
|
|
87
|
+
required=True,
|
|
88
|
+
)
|
|
89
|
+
@save_output_to()
|
|
90
|
+
@file_types([".csv", ".xlsx"])
|
|
91
|
+
def get_inactive_users(days: int, save_output_to: Path, file_type: str):
|
|
92
|
+
"""
|
|
93
|
+
Get users that haven't logged in X days from Okta API and save the output as a .csv or .xlsx file.
|
|
94
|
+
"""
|
|
95
|
+
save_inactive_users_from_okta(days=days, save_output_to=save_output_to, file_type=file_type)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@okta.command(name="get_all_users")
|
|
99
|
+
@save_output_to()
|
|
100
|
+
@file_types([".csv", ".xlsx"])
|
|
101
|
+
def get_all_users(save_output_to: Path, file_type: str):
|
|
102
|
+
"""
|
|
103
|
+
Get All users from Okta API and save the output to a .csv or .xlsx file.
|
|
104
|
+
"""
|
|
105
|
+
save_all_users_from_okta(save_output_to=save_output_to, file_type=file_type)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@okta.command(name="get_new_users")
|
|
109
|
+
@click.option(
|
|
110
|
+
"--days",
|
|
111
|
+
type=click.INT,
|
|
112
|
+
help="The number of days to see if a user has been added to Okta, default is 30",
|
|
113
|
+
default=30,
|
|
114
|
+
required=True,
|
|
115
|
+
)
|
|
116
|
+
@save_output_to()
|
|
117
|
+
@file_types([".csv", ".xlsx"])
|
|
118
|
+
def get_recent_users(days: int, save_output_to: Path, file_type: str):
|
|
119
|
+
"""
|
|
120
|
+
Get users that were added to Okta in X days.
|
|
121
|
+
"""
|
|
122
|
+
save_recently_added_users_from_okta(days=days, save_output_to=save_output_to, file_type=file_type)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@okta.command(name="get_admin_users")
|
|
126
|
+
@save_output_to()
|
|
127
|
+
@file_types([".csv", ".xlsx"])
|
|
128
|
+
def get_admin_users(save_output_to: Path, file_type: str) -> None:
|
|
129
|
+
"""
|
|
130
|
+
Get all admin users from Okta API and save the output to .csv or .xlsx file.
|
|
131
|
+
"""
|
|
132
|
+
save_admin_users_from_okta(save_output_to=save_output_to, file_type=file_type)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def save_active_users_from_okta(save_output_to: Path, file_type: str = ".csv") -> None:
|
|
136
|
+
"""
|
|
137
|
+
Function to get active users from Okta via API and save them to a .csv or .xlsx file
|
|
138
|
+
:param Path save_output_to: The path to save the output file to
|
|
139
|
+
:param str file_type: The file type to save the output file as, default is .csv, options are .csv or .xlsx
|
|
140
|
+
:rtype: None
|
|
141
|
+
"""
|
|
142
|
+
if file_type.lower() not in [".csv", ".xlsx"]:
|
|
143
|
+
error_and_exit("Invalid file type. Please choose .csv or .xlsx.")
|
|
144
|
+
|
|
145
|
+
# Get Status of Client Application
|
|
146
|
+
app = check_license()
|
|
147
|
+
api = Api()
|
|
148
|
+
|
|
149
|
+
# check if RegScale token is valid:
|
|
150
|
+
if is_valid(app=app):
|
|
151
|
+
# get the token type from init.yaml
|
|
152
|
+
auth_type = app.config["oktaApiToken"].split(" ")
|
|
153
|
+
|
|
154
|
+
# authenticate with Okta
|
|
155
|
+
authenticate_with_okta(app, api, auth_type[0])
|
|
156
|
+
|
|
157
|
+
# check file path exists, if not create it
|
|
158
|
+
check_file_path(save_output_to)
|
|
159
|
+
|
|
160
|
+
# start progress bar to let user know tasks are working
|
|
161
|
+
with job_progress:
|
|
162
|
+
logger.info("Fetching active users from Okta.")
|
|
163
|
+
# add task for fetching users from Okta
|
|
164
|
+
fetching_users = job_progress.add_task("[#f8b737]Fetching active users from Okta...", total=1)
|
|
165
|
+
# fetch active users from Okta
|
|
166
|
+
users = get_okta_data(
|
|
167
|
+
api=api,
|
|
168
|
+
url=f"{api.config['oktaUrl']}/api/v1/users",
|
|
169
|
+
headers={
|
|
170
|
+
"Content-Type": 'application/json; okta-response="omitCredentials, omitCredentialsLinks"',
|
|
171
|
+
"Accept": "application/json",
|
|
172
|
+
"Authorization": api.config["oktaApiToken"],
|
|
173
|
+
},
|
|
174
|
+
params=(("filter", 'status eq "ACTIVE"'), ("limit", "200")),
|
|
175
|
+
task=fetching_users,
|
|
176
|
+
)
|
|
177
|
+
# notify user of how many active users we found
|
|
178
|
+
logger.info("Found %s active user(s).", len(users))
|
|
179
|
+
|
|
180
|
+
check_and_save_data(
|
|
181
|
+
data=users,
|
|
182
|
+
file_name="okta_active_users",
|
|
183
|
+
file_path=save_output_to,
|
|
184
|
+
file_type=file_type,
|
|
185
|
+
data_desc="active user(s)",
|
|
186
|
+
)
|
|
187
|
+
# Notify user the RegScale token needs to be updated
|
|
188
|
+
else:
|
|
189
|
+
error_and_exit(LOGIN_ERROR)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def save_inactive_users_from_okta(save_output_to: Path, file_type: str = ".csv", days: int = 30) -> None:
|
|
193
|
+
"""
|
|
194
|
+
Function to get users that haven't logged in X days from Okta API
|
|
195
|
+
and saves the output as a .csv or .xlsx file.
|
|
196
|
+
:param Path save_output_to: The path to save the output file to
|
|
197
|
+
:param str file_type: The file type to save the output file as, defaults to .csv, options are .csv or .xlsx
|
|
198
|
+
:param int days: The number of days to check for inactive users
|
|
199
|
+
:rtype: None
|
|
200
|
+
"""
|
|
201
|
+
if file_type.lower() not in [".csv", ".xlsx"]:
|
|
202
|
+
error_and_exit("Invalid file type. Please choose .csv or .xlsx.")
|
|
203
|
+
|
|
204
|
+
# Get Status of Client Application
|
|
205
|
+
app = check_license()
|
|
206
|
+
api = Api()
|
|
207
|
+
|
|
208
|
+
# check if RegScale token is valid:
|
|
209
|
+
if is_valid(app=app):
|
|
210
|
+
# get the token type from init.yaml
|
|
211
|
+
auth_type = app.config["oktaApiToken"].split(" ")
|
|
212
|
+
|
|
213
|
+
# authenticate with Okta
|
|
214
|
+
authenticate_with_okta(app, api, auth_type[0])
|
|
215
|
+
|
|
216
|
+
# use job_progress for live task progress
|
|
217
|
+
with job_progress:
|
|
218
|
+
# check file path exists, if not create it
|
|
219
|
+
check_file_path(save_output_to)
|
|
220
|
+
|
|
221
|
+
# calculate last login date criteria with days provided
|
|
222
|
+
since_date = datetime.now() - timedelta(days=days)
|
|
223
|
+
|
|
224
|
+
# get all users from Okta
|
|
225
|
+
users = get_all_okta_users(api)
|
|
226
|
+
|
|
227
|
+
# analyze the users
|
|
228
|
+
inactive_users = analyze_okta_users(
|
|
229
|
+
user_list=users,
|
|
230
|
+
key="lastLogin",
|
|
231
|
+
filter_value=since_date,
|
|
232
|
+
user_type="inactive",
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
# check, clean and save the data from okta to provided save_output_to and file_type
|
|
236
|
+
check_and_save_data(
|
|
237
|
+
data=inactive_users,
|
|
238
|
+
file_name="okta_inactive_users",
|
|
239
|
+
file_path=save_output_to,
|
|
240
|
+
file_type=file_type,
|
|
241
|
+
data_desc="inactive user(s)",
|
|
242
|
+
)
|
|
243
|
+
# Notify user the RegScale token needs to be updated
|
|
244
|
+
else:
|
|
245
|
+
error_and_exit(LOGIN_ERROR)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def save_all_users_from_okta(save_output_to: Path, file_type: str = ".csv") -> None:
|
|
249
|
+
"""
|
|
250
|
+
Function to get all users from Okta via API and saves the output to a .csv or .xlsx file
|
|
251
|
+
:param Path save_output_to: The path to save the output file to
|
|
252
|
+
:param str file_type: The file type to save the output file as, defaults to .csv, options are .csv or .xlsx
|
|
253
|
+
:rtype: None
|
|
254
|
+
"""
|
|
255
|
+
if file_type.lower() not in [".csv", ".xlsx"]:
|
|
256
|
+
error_and_exit("Invalid file type. Please choose .csv or .xlsx.")
|
|
257
|
+
|
|
258
|
+
# Get status of client application
|
|
259
|
+
app = check_license()
|
|
260
|
+
api = Api()
|
|
261
|
+
|
|
262
|
+
# check if RegScale token is valid:
|
|
263
|
+
if is_valid(app=app):
|
|
264
|
+
# get the token type from init.yaml
|
|
265
|
+
auth_type = app.config["oktaApiToken"].split(" ")
|
|
266
|
+
|
|
267
|
+
# authenticate with Okta
|
|
268
|
+
authenticate_with_okta(app, api, auth_type[0])
|
|
269
|
+
|
|
270
|
+
# check file path exists, if not create it
|
|
271
|
+
check_file_path(save_output_to)
|
|
272
|
+
|
|
273
|
+
# get all users from Okta
|
|
274
|
+
users = get_all_okta_users(api)
|
|
275
|
+
|
|
276
|
+
# check, clean and save the data from okta to provided save_output_to and file_type
|
|
277
|
+
check_and_save_data(
|
|
278
|
+
data=users,
|
|
279
|
+
file_name="okta_users",
|
|
280
|
+
file_path=save_output_to,
|
|
281
|
+
file_type=file_type,
|
|
282
|
+
data_desc="Okta users",
|
|
283
|
+
)
|
|
284
|
+
# Notify user the RegScale token needs to be updated
|
|
285
|
+
else:
|
|
286
|
+
error_and_exit(LOGIN_ERROR)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def save_recently_added_users_from_okta(save_output_to: Path, file_type: str = ".csv", days: int = 30) -> None:
|
|
290
|
+
"""
|
|
291
|
+
Function to download users added in the last X days from Okta via API, defaults to last 30 days
|
|
292
|
+
and saves the output to a .csv or .xlsx file
|
|
293
|
+
:param Path save_output_to: The path to save the output file to
|
|
294
|
+
:param str file_type: The file type to save the output file as, .csv or .xlsx, defaults to .csv
|
|
295
|
+
:param int days: The number of days to check for recently added users, defaults to 30
|
|
296
|
+
:rtype: None
|
|
297
|
+
"""
|
|
298
|
+
if file_type.lower() not in [".csv", ".xlsx"]:
|
|
299
|
+
error_and_exit("Invalid file type. Please choose .csv or .xlsx.")
|
|
300
|
+
# Get Status of Client Application
|
|
301
|
+
app = check_license()
|
|
302
|
+
api = Api()
|
|
303
|
+
|
|
304
|
+
# check if RegScale token is valid:
|
|
305
|
+
if is_valid(app=app):
|
|
306
|
+
# get the token type from init.yaml
|
|
307
|
+
auth_type = app.config["oktaApiToken"].split(" ")
|
|
308
|
+
|
|
309
|
+
# authenticate with Okta
|
|
310
|
+
authenticate_with_okta(app, api, auth_type[0])
|
|
311
|
+
|
|
312
|
+
# use job_progress for live task progress
|
|
313
|
+
with job_progress:
|
|
314
|
+
# check file path exists, if not create it
|
|
315
|
+
check_file_path(save_output_to)
|
|
316
|
+
|
|
317
|
+
# calculate last login date criteria with days provided
|
|
318
|
+
since_date = datetime.now() - timedelta(days=days)
|
|
319
|
+
|
|
320
|
+
# get all users from Okta
|
|
321
|
+
users = get_all_okta_users(api)
|
|
322
|
+
|
|
323
|
+
# analyze the users
|
|
324
|
+
created_users = analyze_okta_users(
|
|
325
|
+
user_list=users,
|
|
326
|
+
key="created",
|
|
327
|
+
filter_value=since_date,
|
|
328
|
+
user_type="new",
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
# check, clean and save the data from Okta to provided save_output_to and file_type
|
|
332
|
+
check_and_save_data(
|
|
333
|
+
data=created_users,
|
|
334
|
+
file_name="okta_new_users",
|
|
335
|
+
file_path=save_output_to,
|
|
336
|
+
file_type=file_type,
|
|
337
|
+
data_desc="new user(s)",
|
|
338
|
+
)
|
|
339
|
+
# Notify user the RegScale token needs to be updated
|
|
340
|
+
else:
|
|
341
|
+
error_and_exit(LOGIN_ERROR)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def save_admin_users_from_okta(save_output_to: Path, file_type: str = ".csv") -> None:
|
|
345
|
+
"""
|
|
346
|
+
Function to get all admin users from Okta via API and save the output to .csv or .xlsx file
|
|
347
|
+
:param Path save_output_to: The path to save the output file to
|
|
348
|
+
:param str file_type: The file type to save the output file as, defaults to .csv, options are .csv or .xlsx
|
|
349
|
+
:rtype: None
|
|
350
|
+
"""
|
|
351
|
+
if file_type.lower() not in [".csv", ".xlsx"]:
|
|
352
|
+
error_and_exit("Invalid file type. Please choose .csv or .xlsx.")
|
|
353
|
+
|
|
354
|
+
# Get Status of Client Application
|
|
355
|
+
app = check_license()
|
|
356
|
+
api = Api()
|
|
357
|
+
|
|
358
|
+
# check if RegScale token is valid:
|
|
359
|
+
if is_valid(app=app):
|
|
360
|
+
# get the token type from init.yaml
|
|
361
|
+
auth_type = app.config["oktaApiToken"].split(" ")
|
|
362
|
+
|
|
363
|
+
# authenticate with Okta
|
|
364
|
+
authenticate_with_okta(app, api, auth_type[0])
|
|
365
|
+
|
|
366
|
+
# use job_progress for live task progress
|
|
367
|
+
with job_progress:
|
|
368
|
+
# check file path exists, if not create it
|
|
369
|
+
check_file_path(save_output_to)
|
|
370
|
+
|
|
371
|
+
# get all users from Okta
|
|
372
|
+
users = get_all_okta_users(api)
|
|
373
|
+
|
|
374
|
+
# create task for fetching user roles
|
|
375
|
+
fetch_user_roles = job_progress.add_task(
|
|
376
|
+
f"[#ef5d23]Fetching user roles for {len(users)} user(s)...",
|
|
377
|
+
total=len(users),
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
# create threads to get user roles for each user
|
|
381
|
+
create_threads(
|
|
382
|
+
process=get_user_roles,
|
|
383
|
+
args=(api, users, fetch_user_roles),
|
|
384
|
+
thread_count=len(users),
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
# check, clean and save the data from Okta to provided save_output_to and file_type
|
|
388
|
+
check_and_save_data(
|
|
389
|
+
data=admin_users,
|
|
390
|
+
file_name="okta_admin_users",
|
|
391
|
+
file_path=save_output_to,
|
|
392
|
+
file_type=file_type,
|
|
393
|
+
data_desc="admin user(s)",
|
|
394
|
+
)
|
|
395
|
+
# Notify user the RegScale token needs to be updated
|
|
396
|
+
else:
|
|
397
|
+
error_and_exit(LOGIN_ERROR)
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def get_user_roles(args: Tuple, thread: int) -> None:
|
|
401
|
+
"""
|
|
402
|
+
Function used by threads to get the roles for each Okta user
|
|
403
|
+
:param Tuple args: args for the threads to use during the function
|
|
404
|
+
:param int thread: Number of the current thread
|
|
405
|
+
:rtype: None
|
|
406
|
+
"""
|
|
407
|
+
# set up my args from the args tuple
|
|
408
|
+
api, all_users, task = args
|
|
409
|
+
|
|
410
|
+
# set the headers for the Okta API Call
|
|
411
|
+
headers = {
|
|
412
|
+
"Content-Type": 'application/json; okta-response="omitCredentials, omitCredentialsLinks"',
|
|
413
|
+
"Accept": "application/json",
|
|
414
|
+
"Authorization": api.config["oktaApiToken"],
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
# get the thread assignment for the current thread
|
|
418
|
+
threads = thread_assignment(thread=thread, total_items=len(all_users))
|
|
419
|
+
|
|
420
|
+
# fetch the roles from Okta to get all the user's roles
|
|
421
|
+
for i in range(len(threads)):
|
|
422
|
+
user = all_users[threads[i]]
|
|
423
|
+
|
|
424
|
+
# get all the roles for the user
|
|
425
|
+
user_roles = get_okta_data(
|
|
426
|
+
api=api,
|
|
427
|
+
task=task,
|
|
428
|
+
url=f"{api.config['oktaUrl']}/api/v1/users/{user['id']}/roles",
|
|
429
|
+
headers=headers,
|
|
430
|
+
)
|
|
431
|
+
roles = [role["label"] for role in user_roles]
|
|
432
|
+
# add concatenated user roles to their entry in all_users
|
|
433
|
+
user["roles"] = ", ".join(roles)
|
|
434
|
+
|
|
435
|
+
# add user to global admin_user list if, admin is in their concatenated role list
|
|
436
|
+
(admin_users.append(user) if any("admin" in val.lower() for val in roles) else None)
|
|
437
|
+
|
|
438
|
+
# update the progress bar
|
|
439
|
+
job_progress.update(task, advance=1)
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def analyze_okta_users(user_list: list, key: str, filter_value: Any, user_type: str) -> list:
|
|
443
|
+
"""
|
|
444
|
+
Function to analyze users with the provided key and value
|
|
445
|
+
and returns users that match the criteria in a list
|
|
446
|
+
:param list user_list: Initial list of Okta users before being filtered
|
|
447
|
+
:param str key: Key to use to filter the Okta users
|
|
448
|
+
:param Any filter_value: Value used to filter Okta users with provided key
|
|
449
|
+
:param str user_type: Type of user we are filtering for, used for log outputs
|
|
450
|
+
:return: List of filtered Okta users using provided key and value
|
|
451
|
+
:rtype: list
|
|
452
|
+
"""
|
|
453
|
+
logger.info("Analyzing %s Okta user(s).", len(user_list))
|
|
454
|
+
# create list to store the users that match the provided criteria
|
|
455
|
+
filtered_users = []
|
|
456
|
+
|
|
457
|
+
# create task for analyzing user's data
|
|
458
|
+
analyze_login = job_progress.add_task(f"[#ef5d23]Analyzing {len(user_list)} user(s) data...", total=len(user_list))
|
|
459
|
+
# iterate through all users and check user's with the provided criteria
|
|
460
|
+
for user in user_list:
|
|
461
|
+
if data_filter := user[key]:
|
|
462
|
+
# verify comparing filter_value date against a string
|
|
463
|
+
if isinstance(filter_value, datetime) and isinstance(data_filter, str):
|
|
464
|
+
# try to convert it
|
|
465
|
+
try:
|
|
466
|
+
data_filter = datetime.strptime(data_filter, "%Y-%m-%dT%H:%M:%S.%fZ")
|
|
467
|
+
# if an error is encountered, make it an old date
|
|
468
|
+
except (TypeError, KeyError, AttributeError):
|
|
469
|
+
error_and_exit("Incorrect date format. Please follow this format: '%Y-%m-%dT%H:%M:%S.%fZ'")
|
|
470
|
+
# compare the values as date objects instead of datetime objects
|
|
471
|
+
# if the user_type provided is inactive, make sure the provided date
|
|
472
|
+
# is between the correct date field and today
|
|
473
|
+
compare_dates_and_user_type(user, filtered_users, filter_value, user_type, data_filter, datetime.now())
|
|
474
|
+
elif data_filter is None:
|
|
475
|
+
filtered_users.append(user)
|
|
476
|
+
# update analyzing user task
|
|
477
|
+
job_progress.update(analyze_login, advance=1)
|
|
478
|
+
# notify user of how many inactive users we found
|
|
479
|
+
logger.info("Found %s %s user(s) in Okta.", len(filtered_users), user_type)
|
|
480
|
+
|
|
481
|
+
# return the users that match the provided criteria
|
|
482
|
+
return filtered_users
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def compare_dates_and_user_type(
|
|
486
|
+
user: dict, filtered_users: list, filter_value: datetime, user_type: str, data_filter: Any, today: datetime
|
|
487
|
+
):
|
|
488
|
+
"""
|
|
489
|
+
Function to determine if the user matches the provided criteria and adds them to the filtered_users list
|
|
490
|
+
|
|
491
|
+
:param dict user: User to determine if they match the provided criteria
|
|
492
|
+
:param list filtered_users: List of users that match the provided criteria
|
|
493
|
+
:param datetime filter_value: Date to compare the user's date with
|
|
494
|
+
:param str user_type: Type of user we are filtering for, used to determine the correct date logic
|
|
495
|
+
:param Any data_filter: Date to compare the user's date with
|
|
496
|
+
:param datetime today: Today's date
|
|
497
|
+
"""
|
|
498
|
+
# compare the values as date objects instead of datetime objects
|
|
499
|
+
# if the user_type provided is inactive, make sure the provided date
|
|
500
|
+
# is between the correct date field and today
|
|
501
|
+
if user_type == "inactive" and filter_value.date() >= data_filter.date() <= today.date():
|
|
502
|
+
# add user to filtered users list
|
|
503
|
+
filtered_users.append(user)
|
|
504
|
+
elif user_type == "new" and filter_value.date() <= data_filter.date() <= today.date():
|
|
505
|
+
# add user to filtered users list
|
|
506
|
+
filtered_users.append(user)
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
def check_and_save_data(data: list, file_name: str, file_path: Path, file_type: str, data_desc: str) -> None:
|
|
510
|
+
"""
|
|
511
|
+
Function to check data returned from Okta API and cleans the response data and will save it
|
|
512
|
+
to the provided file_path as the provided file_type
|
|
513
|
+
:param list data: Raw data returned from Okta API
|
|
514
|
+
:param str file_name: Desired name of the output file
|
|
515
|
+
:param Path file_path: Directory to save the cleaned data to
|
|
516
|
+
:param str file_type: Desired file type to output the data to
|
|
517
|
+
:param str data_desc: Description of the data, used in output and logs
|
|
518
|
+
:rtype: None
|
|
519
|
+
"""
|
|
520
|
+
logger.info("Starting to clean and format data from Okta.")
|
|
521
|
+
|
|
522
|
+
# check Okta data has data
|
|
523
|
+
if len(data) >= 1:
|
|
524
|
+
# use job_progress for live task progress
|
|
525
|
+
with job_progress:
|
|
526
|
+
# generate file name with today's date
|
|
527
|
+
file_name = f"{file_name}_{get_current_datetime('%m%d%Y')}"
|
|
528
|
+
|
|
529
|
+
# create task for cleaning the data response from Okta
|
|
530
|
+
clean_data_task = job_progress.add_task("[#21a5bb]Cleaning data from Okta...", total=1)
|
|
531
|
+
# clean the data from Okta
|
|
532
|
+
clean_data = clean_okta_output(data=data, skip_keys=["_links"])
|
|
533
|
+
|
|
534
|
+
# update the task as complete
|
|
535
|
+
job_progress.update(clean_data_task, advance=1)
|
|
536
|
+
|
|
537
|
+
# create task for saving file
|
|
538
|
+
saving_file_task = job_progress.add_task(
|
|
539
|
+
f"[#0866b4]Saving {len(clean_data)} {data_desc} to {file_path}/{file_name}{file_type}...",
|
|
540
|
+
total=1,
|
|
541
|
+
)
|
|
542
|
+
# save the output to the provided file_path
|
|
543
|
+
save_data_to(
|
|
544
|
+
file=Path(f"{file_path}/{file_name}{file_type}"),
|
|
545
|
+
data=clean_data,
|
|
546
|
+
)
|
|
547
|
+
# mark saving_file_task as complete
|
|
548
|
+
job_progress.update(saving_file_task, advance=1)
|
|
549
|
+
logger.info(
|
|
550
|
+
"Saved %s %s successfully to %s%s!",
|
|
551
|
+
len(clean_data),
|
|
552
|
+
data_desc,
|
|
553
|
+
file_path,
|
|
554
|
+
file_type,
|
|
555
|
+
)
|
|
556
|
+
else:
|
|
557
|
+
logger.info("No %s to save to %s!", data_desc, file_path)
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
def get_all_okta_users(api: Api) -> list:
|
|
561
|
+
"""
|
|
562
|
+
Function to get all Okta users using the Okta API
|
|
563
|
+
|
|
564
|
+
:param Api api: API object
|
|
565
|
+
:return: List of all Okta users
|
|
566
|
+
:rtype: list
|
|
567
|
+
"""
|
|
568
|
+
logger.info("Fetching all users from Okta.")
|
|
569
|
+
# add task for fetching users from Okta
|
|
570
|
+
fetching_users = job_progress.add_task("[#f8b737]Fetching all users from Okta...", total=1)
|
|
571
|
+
|
|
572
|
+
# fetch active users from Okta
|
|
573
|
+
users = get_okta_data(
|
|
574
|
+
api=api,
|
|
575
|
+
url=f"{api.config['oktaUrl']}/api/v1/users",
|
|
576
|
+
headers={
|
|
577
|
+
"Content-Type": 'application/json; okta-response="omitCredentials, omitCredentialsLinks"',
|
|
578
|
+
"Accept": "application/json",
|
|
579
|
+
"Authorization": api.config["oktaApiToken"],
|
|
580
|
+
},
|
|
581
|
+
task=fetching_users,
|
|
582
|
+
)
|
|
583
|
+
# notify user of how many active users we found
|
|
584
|
+
logger.info("Found %s Okta user(s).", len(users))
|
|
585
|
+
# return all users from Okta
|
|
586
|
+
return users
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
def get_okta_data(api: Api, task: int, url: str, headers: dict, params: Tuple = None) -> list:
|
|
590
|
+
"""
|
|
591
|
+
Function to use the Okta core API to get data, also handles pagination for Okta requests
|
|
592
|
+
|
|
593
|
+
:param Api api: API object
|
|
594
|
+
:param int task: task to update to show user live progress
|
|
595
|
+
:param str url: URL to use while using Okta API
|
|
596
|
+
:param dict headers: Headers for the Okta API call
|
|
597
|
+
:param Tuple params: Parameters for Okta API call, defaults to None
|
|
598
|
+
:return: List of data received from the API call to Okta
|
|
599
|
+
:rtype: list
|
|
600
|
+
"""
|
|
601
|
+
# get data from Okta with provided information
|
|
602
|
+
okta_response = api.get(url=url, headers=headers, params=params)
|
|
603
|
+
# check the response
|
|
604
|
+
if okta_response.status_code == 403:
|
|
605
|
+
error_and_exit(
|
|
606
|
+
"RegScale CLI wasn't granted the necessary permissions for this action."
|
|
607
|
+
+ "Please verify permissions in Okta admin portal and try again."
|
|
608
|
+
)
|
|
609
|
+
elif okta_response.status_code != 200:
|
|
610
|
+
error_and_exit(
|
|
611
|
+
f"Received unexpected response from Okta API.\n{okta_response.status_code}: {okta_response.text}"
|
|
612
|
+
)
|
|
613
|
+
try:
|
|
614
|
+
# try to read the response and convert it to a JSON object
|
|
615
|
+
okta_data = okta_response.json()
|
|
616
|
+
except JSONDecodeError as ex:
|
|
617
|
+
# notify user if there was a json decode error from API response and exit
|
|
618
|
+
error_and_exit(f"JSON decode error.\n{ex}")
|
|
619
|
+
# check if pagination required to fetch all data
|
|
620
|
+
response_links = okta_response.headers.get("link")
|
|
621
|
+
if okta_response.status_code == 200 and "next" in response_links:
|
|
622
|
+
# get the url for the next pagination
|
|
623
|
+
url = parse_url_for_pagination(response_links)
|
|
624
|
+
|
|
625
|
+
# get the next page of data
|
|
626
|
+
okta_data.extend(get_okta_data(api=api, task=task, url=url, headers=headers))
|
|
627
|
+
elif okta_response.status_code != 200:
|
|
628
|
+
error_and_exit(
|
|
629
|
+
f"Received unexpected response from Okta!\nReceived: {okta_response.status_code}\n{okta_response.text}"
|
|
630
|
+
)
|
|
631
|
+
# mark the provided task as complete
|
|
632
|
+
job_progress.update(task, advance=1)
|
|
633
|
+
|
|
634
|
+
# return the Okta data
|
|
635
|
+
return okta_data
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
def clean_okta_output(data: list, skip_keys: list = None) -> dict:
|
|
639
|
+
"""
|
|
640
|
+
Function to remove nested dictionaries and the provided skip_key of the
|
|
641
|
+
list data from Okta API response and returns a clean dictionary without any nested dictionaries
|
|
642
|
+
|
|
643
|
+
:param list data: List of raw data from Okta
|
|
644
|
+
:param list skip_keys: List of Keys to skip while cleaning the raw data
|
|
645
|
+
:return: Dictionary of clean Okta data
|
|
646
|
+
:rtype: dict
|
|
647
|
+
"""
|
|
648
|
+
logger.info("Cleaning Okta data.")
|
|
649
|
+
logger.debug("\nRaw data: %s\n", data)
|
|
650
|
+
# create empty dictionary to store clean data
|
|
651
|
+
clean_data = {}
|
|
652
|
+
# iterate through each item in the provided list
|
|
653
|
+
for row in data:
|
|
654
|
+
# get a row of data with nested dictionaries as key value pairs
|
|
655
|
+
# while also remove the _links nested dictionary
|
|
656
|
+
new_row_data = remove_nested_dict(data=row, skip_keys=skip_keys)
|
|
657
|
+
# iterate through the original data of nested dicts and remove them
|
|
658
|
+
# from our clean data
|
|
659
|
+
for item in reversed(row):
|
|
660
|
+
# check if the item is a nested dictionary and exists in our clean data
|
|
661
|
+
if isinstance(row[item], dict) and item in new_row_data:
|
|
662
|
+
# remove the nested dictionary from the clean data
|
|
663
|
+
del new_row_data[item]
|
|
664
|
+
# update the old data with the new data
|
|
665
|
+
clean_data[data.index(row)] = new_row_data
|
|
666
|
+
logger.info("Okta data has been cleaned successfully.")
|
|
667
|
+
logger.debug("\nClean data: %s\n", clean_data)
|
|
668
|
+
# return the clean data set without nested dictionaries
|
|
669
|
+
return clean_data
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
def authenticate_with_okta(app: Application, api: Api, type: str) -> None:
|
|
673
|
+
"""
|
|
674
|
+
Function to authenticate with Okta via API and the provided method in type
|
|
675
|
+
|
|
676
|
+
:param Application app: Application object
|
|
677
|
+
:param Api api: API object
|
|
678
|
+
:param str type: type of authentication for the Okta API
|
|
679
|
+
:rtype: None
|
|
680
|
+
"""
|
|
681
|
+
config = app.config
|
|
682
|
+
if type.lower() == "ssws":
|
|
683
|
+
# verify the provided SSWS token from init.yaml
|
|
684
|
+
verify_response = api.get(
|
|
685
|
+
url=f"{config['oktaUrl']}/api/v1/users",
|
|
686
|
+
headers={
|
|
687
|
+
"Content-Type": 'application/json; okta-response="omitCredentials, omitCredentialsLinks"',
|
|
688
|
+
"Accept": "application/json",
|
|
689
|
+
"Authorization": config["oktaApiToken"],
|
|
690
|
+
},
|
|
691
|
+
)
|
|
692
|
+
if verify_response.ok:
|
|
693
|
+
logger.info("Okta SSWS Token has been verified.")
|
|
694
|
+
else:
|
|
695
|
+
error_and_exit(
|
|
696
|
+
"Please verify SSWS Token from Okta is entered correctly in init.yaml, "
|
|
697
|
+
+ "and it has okta.users.read & okta.roles.read permissions granted and try again."
|
|
698
|
+
)
|
|
699
|
+
elif type.lower() == "bearer":
|
|
700
|
+
# check if secret key is in the init.yaml config
|
|
701
|
+
key = config.get("oktaSecretKey")
|
|
702
|
+
|
|
703
|
+
# if it exists try to get a bearer token from Okta API
|
|
704
|
+
if key:
|
|
705
|
+
token = get_okta_token(config=config, api=api, app=app)
|
|
706
|
+
logger.info("New Okta Token: %s", token)
|
|
707
|
+
else:
|
|
708
|
+
# create the init.yaml entry for the oktaSecretKey and prompt user to get it from admin portal
|
|
709
|
+
config["oktaSecretKey"] = {
|
|
710
|
+
"d": "get from Okta",
|
|
711
|
+
"p": "get from Okta",
|
|
712
|
+
"q": "get from Okta",
|
|
713
|
+
"dp": "get from Okta",
|
|
714
|
+
"dq": "get from Okta",
|
|
715
|
+
"qi": "get from Okta",
|
|
716
|
+
"kty": "get from Okta",
|
|
717
|
+
"e": "get from Okta",
|
|
718
|
+
"kid": "get from Okta",
|
|
719
|
+
"n": "get from Okta",
|
|
720
|
+
}
|
|
721
|
+
config["oktaScopes"] = "okta.users.read okta.roles.read"
|
|
722
|
+
app.save_config(config)
|
|
723
|
+
logger.info(
|
|
724
|
+
"Please enter the private key for the application created in Okta admin"
|
|
725
|
+
+ "portal into init.yaml file and try again."
|
|
726
|
+
)
|
|
727
|
+
else:
|
|
728
|
+
error_and_exit(
|
|
729
|
+
"Please enter a valid authentication type for Okta API and try again. Please choose from SSWS or Bearer."
|
|
730
|
+
)
|
|
731
|
+
|
|
732
|
+
|
|
733
|
+
def get_okta_token(config: dict, api: Api, app: Application) -> str:
|
|
734
|
+
"""
|
|
735
|
+
Function to create a JWT to get a bearer token from Okta via API
|
|
736
|
+
|
|
737
|
+
:param dict config: Application configuration (init.yaml)
|
|
738
|
+
:param Api api: API object
|
|
739
|
+
:param Application app: Application object
|
|
740
|
+
:return: JWT token for future requests
|
|
741
|
+
:rtype: str
|
|
742
|
+
"""
|
|
743
|
+
okta_token = ""
|
|
744
|
+
|
|
745
|
+
# get the Okta private JWK
|
|
746
|
+
jwk_token = jwk.JWK.from_json(json.dumps(config["oktaSecretKey"]))
|
|
747
|
+
|
|
748
|
+
# get the url from config without any trailing /
|
|
749
|
+
url = config["oktaUrl"].strip("/") + "/oauth2/v1/token"
|
|
750
|
+
|
|
751
|
+
# set the payload for the to be signed JWT while setting the signed JWT to expire in 10 minutes
|
|
752
|
+
payload_data = {
|
|
753
|
+
"aud": url,
|
|
754
|
+
"iss": config["oktaClientId"],
|
|
755
|
+
"sub": config["oktaClientId"],
|
|
756
|
+
"exp": int(time.time()) + 600,
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
# create a signed JWT
|
|
760
|
+
token = python_jwt.generate_jwt(payload_data, jwk_token, "RS256", timedelta(minutes=5))
|
|
761
|
+
|
|
762
|
+
# set the headers for the API call
|
|
763
|
+
headers = {
|
|
764
|
+
"Accept": "application/json",
|
|
765
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
# set up the data to the expected format for Okta API call
|
|
769
|
+
payload = (
|
|
770
|
+
f'grant_type=client_credentials&scope={config["oktaScopes"]}&client_assertion_type='
|
|
771
|
+
+ f"urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&client_assertion={token}"
|
|
772
|
+
)
|
|
773
|
+
|
|
774
|
+
# post the data to get a bearer token for future requests
|
|
775
|
+
token_response = api.post(url=url, headers=headers, data=payload)
|
|
776
|
+
|
|
777
|
+
# see if the API call was successful
|
|
778
|
+
if token_response.status_code == 200:
|
|
779
|
+
try:
|
|
780
|
+
# convert response to a JSON object
|
|
781
|
+
token = token_response.json()
|
|
782
|
+
|
|
783
|
+
# format the bearer token returned
|
|
784
|
+
okta_token = f'{token["token_type"]} {token["access_token"]}'
|
|
785
|
+
|
|
786
|
+
# update the config with the newly received JWT
|
|
787
|
+
config["oktaApiToken"] = okta_token
|
|
788
|
+
|
|
789
|
+
# save it to init.yaml
|
|
790
|
+
app.save_config(config)
|
|
791
|
+
except JSONDecodeError:
|
|
792
|
+
# unable to convert the API response to a json object
|
|
793
|
+
error_and_exit("Unable to retrieve data from Okta API.")
|
|
794
|
+
else:
|
|
795
|
+
error_and_exit(
|
|
796
|
+
f"Received unexpected response from Okta API.\n{token_response.status_code}: {token_response.text}\n{token}"
|
|
797
|
+
)
|
|
798
|
+
return okta_token
|