regscale-cli 6.21.2.0__py3-none-any.whl → 6.28.2.1__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.
- regscale/_version.py +1 -1
- regscale/airflow/hierarchy.py +2 -2
- regscale/core/app/api.py +5 -2
- regscale/core/app/application.py +36 -6
- regscale/core/app/internal/control_editor.py +73 -21
- regscale/core/app/internal/evidence.py +727 -204
- regscale/core/app/internal/login.py +4 -2
- regscale/core/app/internal/model_editor.py +219 -64
- regscale/core/app/utils/app_utils.py +86 -12
- regscale/core/app/utils/catalog_utils/common.py +1 -1
- regscale/core/login.py +21 -4
- regscale/core/utils/async_graphql_client.py +363 -0
- regscale/core/utils/date.py +77 -1
- regscale/dev/cli.py +26 -0
- regscale/dev/code_gen.py +109 -24
- regscale/dev/version.py +72 -0
- regscale/integrations/commercial/__init__.py +30 -2
- regscale/integrations/commercial/aws/audit_manager_compliance.py +3908 -0
- regscale/integrations/commercial/aws/cli.py +3107 -54
- regscale/integrations/commercial/aws/cloudtrail_control_mappings.py +333 -0
- regscale/integrations/commercial/aws/cloudtrail_evidence.py +501 -0
- regscale/integrations/commercial/aws/cloudwatch_control_mappings.py +357 -0
- regscale/integrations/commercial/aws/cloudwatch_evidence.py +490 -0
- regscale/integrations/commercial/{amazon → aws}/common.py +71 -19
- regscale/integrations/commercial/aws/config_compliance.py +914 -0
- regscale/integrations/commercial/aws/conformance_pack_mappings.py +198 -0
- regscale/integrations/commercial/aws/control_compliance_analyzer.py +439 -0
- regscale/integrations/commercial/aws/evidence_generator.py +283 -0
- regscale/integrations/commercial/aws/guardduty_control_mappings.py +340 -0
- regscale/integrations/commercial/aws/guardduty_evidence.py +1053 -0
- regscale/integrations/commercial/aws/iam_control_mappings.py +368 -0
- regscale/integrations/commercial/aws/iam_evidence.py +574 -0
- regscale/integrations/commercial/aws/inventory/__init__.py +338 -22
- regscale/integrations/commercial/aws/inventory/base.py +107 -5
- regscale/integrations/commercial/aws/inventory/resources/analytics.py +390 -0
- regscale/integrations/commercial/aws/inventory/resources/applications.py +234 -0
- regscale/integrations/commercial/aws/inventory/resources/audit_manager.py +513 -0
- regscale/integrations/commercial/aws/inventory/resources/cloudtrail.py +315 -0
- regscale/integrations/commercial/aws/inventory/resources/cloudtrail_logs_metadata.py +476 -0
- regscale/integrations/commercial/aws/inventory/resources/cloudwatch.py +191 -0
- regscale/integrations/commercial/aws/inventory/resources/compute.py +328 -9
- regscale/integrations/commercial/aws/inventory/resources/config.py +464 -0
- regscale/integrations/commercial/aws/inventory/resources/containers.py +74 -9
- regscale/integrations/commercial/aws/inventory/resources/database.py +481 -31
- regscale/integrations/commercial/aws/inventory/resources/developer_tools.py +253 -0
- regscale/integrations/commercial/aws/inventory/resources/guardduty.py +286 -0
- regscale/integrations/commercial/aws/inventory/resources/iam.py +470 -0
- regscale/integrations/commercial/aws/inventory/resources/inspector.py +476 -0
- regscale/integrations/commercial/aws/inventory/resources/integration.py +175 -61
- regscale/integrations/commercial/aws/inventory/resources/kms.py +447 -0
- regscale/integrations/commercial/aws/inventory/resources/machine_learning.py +358 -0
- regscale/integrations/commercial/aws/inventory/resources/networking.py +390 -67
- regscale/integrations/commercial/aws/inventory/resources/s3.py +394 -0
- regscale/integrations/commercial/aws/inventory/resources/security.py +268 -72
- regscale/integrations/commercial/aws/inventory/resources/securityhub.py +473 -0
- regscale/integrations/commercial/aws/inventory/resources/storage.py +288 -29
- regscale/integrations/commercial/aws/inventory/resources/systems_manager.py +657 -0
- regscale/integrations/commercial/aws/inventory/resources/vpc.py +655 -0
- regscale/integrations/commercial/aws/kms_control_mappings.py +288 -0
- regscale/integrations/commercial/aws/kms_evidence.py +879 -0
- regscale/integrations/commercial/aws/ocsf/__init__.py +7 -0
- regscale/integrations/commercial/aws/ocsf/constants.py +115 -0
- regscale/integrations/commercial/aws/ocsf/mapper.py +435 -0
- regscale/integrations/commercial/aws/org_control_mappings.py +286 -0
- regscale/integrations/commercial/aws/org_evidence.py +666 -0
- regscale/integrations/commercial/aws/s3_control_mappings.py +356 -0
- regscale/integrations/commercial/aws/s3_evidence.py +632 -0
- regscale/integrations/commercial/aws/scanner.py +1072 -205
- regscale/integrations/commercial/aws/security_hub.py +319 -0
- regscale/integrations/commercial/aws/session_manager.py +282 -0
- regscale/integrations/commercial/aws/ssm_control_mappings.py +291 -0
- regscale/integrations/commercial/aws/ssm_evidence.py +492 -0
- regscale/integrations/commercial/jira.py +489 -153
- regscale/integrations/commercial/microsoft_defender/defender.py +326 -5
- regscale/integrations/commercial/microsoft_defender/defender_api.py +348 -14
- regscale/integrations/commercial/microsoft_defender/defender_constants.py +157 -0
- regscale/integrations/commercial/qualys/__init__.py +167 -68
- regscale/integrations/commercial/qualys/scanner.py +305 -39
- regscale/integrations/commercial/sarif/sairf_importer.py +432 -0
- regscale/integrations/commercial/sarif/sarif_converter.py +67 -0
- regscale/integrations/commercial/sicura/api.py +79 -42
- regscale/integrations/commercial/sicura/commands.py +8 -2
- regscale/integrations/commercial/sicura/scanner.py +83 -44
- regscale/integrations/commercial/stigv2/ckl_parser.py +5 -5
- regscale/integrations/commercial/synqly/assets.py +133 -16
- regscale/integrations/commercial/synqly/edr.py +2 -8
- regscale/integrations/commercial/synqly/query_builder.py +536 -0
- regscale/integrations/commercial/synqly/ticketing.py +27 -0
- regscale/integrations/commercial/synqly/vulnerabilities.py +165 -28
- regscale/integrations/commercial/tenablev2/cis_parsers.py +453 -0
- regscale/integrations/commercial/tenablev2/cis_scanner.py +447 -0
- regscale/integrations/commercial/tenablev2/commands.py +146 -5
- regscale/integrations/commercial/tenablev2/scanner.py +1 -3
- regscale/integrations/commercial/tenablev2/stig_parsers.py +113 -57
- regscale/integrations/commercial/wizv2/WizDataMixin.py +1 -1
- regscale/integrations/commercial/wizv2/click.py +191 -76
- regscale/integrations/commercial/wizv2/compliance/__init__.py +15 -0
- regscale/integrations/commercial/wizv2/{policy_compliance_helpers.py → compliance/helpers.py} +78 -60
- regscale/integrations/commercial/wizv2/compliance_report.py +1592 -0
- regscale/integrations/commercial/wizv2/core/__init__.py +133 -0
- regscale/integrations/commercial/wizv2/{async_client.py → core/client.py} +7 -3
- regscale/integrations/commercial/wizv2/{constants.py → core/constants.py} +92 -89
- regscale/integrations/commercial/wizv2/core/file_operations.py +237 -0
- regscale/integrations/commercial/wizv2/fetchers/__init__.py +11 -0
- regscale/integrations/commercial/wizv2/{data_fetcher.py → fetchers/policy_assessment.py} +66 -9
- regscale/integrations/commercial/wizv2/file_cleanup.py +104 -0
- regscale/integrations/commercial/wizv2/issue.py +776 -28
- regscale/integrations/commercial/wizv2/models/__init__.py +0 -0
- regscale/integrations/commercial/wizv2/parsers/__init__.py +34 -0
- regscale/integrations/commercial/wizv2/{parsers.py → parsers/main.py} +1 -1
- regscale/integrations/commercial/wizv2/processors/__init__.py +11 -0
- regscale/integrations/commercial/wizv2/{finding_processor.py → processors/finding.py} +1 -1
- regscale/integrations/commercial/wizv2/reports.py +243 -0
- regscale/integrations/commercial/wizv2/sbom.py +1 -1
- regscale/integrations/commercial/wizv2/scanner.py +1031 -441
- regscale/integrations/commercial/wizv2/utils/__init__.py +48 -0
- regscale/integrations/commercial/wizv2/{utils.py → utils/main.py} +116 -61
- regscale/integrations/commercial/wizv2/variables.py +89 -3
- regscale/integrations/compliance_integration.py +1036 -151
- regscale/integrations/control_matcher.py +432 -0
- regscale/integrations/due_date_handler.py +333 -0
- regscale/integrations/milestone_manager.py +291 -0
- regscale/integrations/public/__init__.py +14 -0
- regscale/integrations/public/cci_importer.py +834 -0
- regscale/integrations/public/csam/__init__.py +0 -0
- regscale/integrations/public/csam/csam.py +938 -0
- regscale/integrations/public/csam/csam_agency_defined.py +179 -0
- regscale/integrations/public/csam/csam_common.py +154 -0
- regscale/integrations/public/csam/csam_controls.py +432 -0
- regscale/integrations/public/csam/csam_poam.py +124 -0
- regscale/integrations/public/fedramp/click.py +77 -6
- regscale/integrations/public/fedramp/docx_parser.py +10 -1
- regscale/integrations/public/fedramp/fedramp_cis_crm.py +675 -289
- regscale/integrations/public/fedramp/fedramp_five.py +1 -1
- regscale/integrations/public/fedramp/poam/scanner.py +75 -7
- regscale/integrations/public/fedramp/poam_export_v5.py +888 -0
- regscale/integrations/scanner_integration.py +1961 -430
- regscale/models/integration_models/CCI_List.xml +1 -0
- regscale/models/integration_models/aqua.py +2 -2
- regscale/models/integration_models/cisa_kev_data.json +805 -11
- regscale/models/integration_models/flat_file_importer/__init__.py +5 -8
- regscale/models/integration_models/nexpose.py +36 -10
- regscale/models/integration_models/qualys.py +3 -4
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +87 -18
- regscale/models/integration_models/synqly_models/filter_parser.py +332 -0
- regscale/models/integration_models/synqly_models/ocsf_mapper.py +124 -25
- regscale/models/integration_models/synqly_models/synqly_model.py +89 -16
- regscale/models/locking.py +12 -8
- regscale/models/platform.py +4 -2
- regscale/models/regscale_models/__init__.py +7 -0
- regscale/models/regscale_models/assessment.py +2 -1
- regscale/models/regscale_models/catalog.py +1 -1
- regscale/models/regscale_models/compliance_settings.py +251 -1
- regscale/models/regscale_models/component.py +1 -0
- regscale/models/regscale_models/control_implementation.py +236 -41
- regscale/models/regscale_models/control_objective.py +74 -5
- regscale/models/regscale_models/file.py +2 -0
- regscale/models/regscale_models/form_field_value.py +5 -3
- regscale/models/regscale_models/inheritance.py +44 -0
- regscale/models/regscale_models/issue.py +301 -102
- regscale/models/regscale_models/milestone.py +33 -14
- regscale/models/regscale_models/organization.py +3 -0
- regscale/models/regscale_models/regscale_model.py +310 -73
- regscale/models/regscale_models/security_plan.py +4 -2
- regscale/models/regscale_models/vulnerability.py +3 -3
- regscale/regscale.py +25 -4
- regscale/templates/__init__.py +0 -0
- regscale/utils/threading/threadhandler.py +20 -15
- regscale/validation/record.py +23 -1
- {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/METADATA +17 -33
- {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/RECORD +310 -111
- tests/core/__init__.py +0 -0
- tests/core/utils/__init__.py +0 -0
- tests/core/utils/test_async_graphql_client.py +472 -0
- tests/fixtures/test_fixture.py +13 -8
- tests/regscale/core/test_login.py +171 -4
- tests/regscale/integrations/commercial/__init__.py +0 -0
- tests/regscale/integrations/commercial/aws/__init__.py +0 -0
- tests/regscale/integrations/commercial/aws/test_audit_manager_compliance.py +1304 -0
- tests/regscale/integrations/commercial/aws/test_audit_manager_evidence_aggregation.py +341 -0
- tests/regscale/integrations/commercial/aws/test_aws_analytics_collector.py +260 -0
- tests/regscale/integrations/commercial/aws/test_aws_applications_collector.py +242 -0
- tests/regscale/integrations/commercial/aws/test_aws_audit_manager_collector.py +1155 -0
- tests/regscale/integrations/commercial/aws/test_aws_cloudtrail_collector.py +534 -0
- tests/regscale/integrations/commercial/aws/test_aws_config_collector.py +400 -0
- tests/regscale/integrations/commercial/aws/test_aws_developer_tools_collector.py +203 -0
- tests/regscale/integrations/commercial/aws/test_aws_guardduty_collector.py +315 -0
- tests/regscale/integrations/commercial/aws/test_aws_iam_collector.py +458 -0
- tests/regscale/integrations/commercial/aws/test_aws_inspector_collector.py +353 -0
- tests/regscale/integrations/commercial/aws/test_aws_inventory_integration.py +530 -0
- tests/regscale/integrations/commercial/aws/test_aws_kms_collector.py +919 -0
- tests/regscale/integrations/commercial/aws/test_aws_machine_learning_collector.py +237 -0
- tests/regscale/integrations/commercial/aws/test_aws_s3_collector.py +722 -0
- tests/regscale/integrations/commercial/aws/test_aws_scanner_integration.py +722 -0
- tests/regscale/integrations/commercial/aws/test_aws_securityhub_collector.py +792 -0
- tests/regscale/integrations/commercial/aws/test_aws_systems_manager_collector.py +918 -0
- tests/regscale/integrations/commercial/aws/test_aws_vpc_collector.py +996 -0
- tests/regscale/integrations/commercial/aws/test_cli_evidence.py +431 -0
- tests/regscale/integrations/commercial/aws/test_cloudtrail_control_mappings.py +452 -0
- tests/regscale/integrations/commercial/aws/test_cloudtrail_evidence.py +788 -0
- tests/regscale/integrations/commercial/aws/test_config_compliance.py +298 -0
- tests/regscale/integrations/commercial/aws/test_conformance_pack_mappings.py +200 -0
- tests/regscale/integrations/commercial/aws/test_control_compliance_analyzer.py +375 -0
- tests/regscale/integrations/commercial/aws/test_datetime_parsing.py +223 -0
- tests/regscale/integrations/commercial/aws/test_evidence_generator.py +386 -0
- tests/regscale/integrations/commercial/aws/test_guardduty_control_mappings.py +564 -0
- tests/regscale/integrations/commercial/aws/test_guardduty_evidence.py +1041 -0
- tests/regscale/integrations/commercial/aws/test_iam_control_mappings.py +718 -0
- tests/regscale/integrations/commercial/aws/test_iam_evidence.py +1375 -0
- tests/regscale/integrations/commercial/aws/test_kms_control_mappings.py +656 -0
- tests/regscale/integrations/commercial/aws/test_kms_evidence.py +1163 -0
- tests/regscale/integrations/commercial/aws/test_ocsf_mapper.py +370 -0
- tests/regscale/integrations/commercial/aws/test_org_control_mappings.py +546 -0
- tests/regscale/integrations/commercial/aws/test_org_evidence.py +1240 -0
- tests/regscale/integrations/commercial/aws/test_s3_control_mappings.py +672 -0
- tests/regscale/integrations/commercial/aws/test_s3_evidence.py +987 -0
- tests/regscale/integrations/commercial/aws/test_scanner_evidence.py +373 -0
- tests/regscale/integrations/commercial/aws/test_security_hub_config_filtering.py +539 -0
- tests/regscale/integrations/commercial/aws/test_session_manager.py +516 -0
- tests/regscale/integrations/commercial/aws/test_ssm_control_mappings.py +588 -0
- tests/regscale/integrations/commercial/aws/test_ssm_evidence.py +735 -0
- tests/regscale/integrations/commercial/conftest.py +28 -0
- tests/regscale/integrations/commercial/microsoft_defender/__init__.py +1 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender.py +1517 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_api.py +1748 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_constants.py +327 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_scanner.py +487 -0
- tests/regscale/integrations/commercial/test_aws.py +3742 -0
- tests/regscale/integrations/commercial/test_burp.py +48 -0
- tests/regscale/integrations/commercial/test_crowdstrike.py +49 -0
- tests/regscale/integrations/commercial/test_dependabot.py +341 -0
- tests/regscale/integrations/commercial/test_gcp.py +1543 -0
- tests/regscale/integrations/commercial/test_gitlab.py +549 -0
- tests/regscale/integrations/commercial/test_ip_mac_address_length.py +84 -0
- tests/regscale/integrations/commercial/test_jira.py +2204 -0
- tests/regscale/integrations/commercial/test_npm_audit.py +42 -0
- tests/regscale/integrations/commercial/test_okta.py +1228 -0
- tests/regscale/integrations/commercial/test_sarif_converter.py +251 -0
- tests/regscale/integrations/commercial/test_sicura.py +349 -0
- tests/regscale/integrations/commercial/test_snow.py +423 -0
- tests/regscale/integrations/commercial/test_sonarcloud.py +394 -0
- tests/regscale/integrations/commercial/test_sqlserver.py +186 -0
- tests/regscale/integrations/commercial/test_stig.py +33 -0
- tests/regscale/integrations/commercial/test_stig_mapper.py +153 -0
- tests/regscale/integrations/commercial/test_stigv2.py +406 -0
- tests/regscale/integrations/commercial/test_wiz.py +1365 -0
- tests/regscale/integrations/commercial/test_wiz_inventory.py +256 -0
- tests/regscale/integrations/commercial/wizv2/__init__.py +339 -0
- tests/regscale/integrations/commercial/wizv2/compliance/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/compliance/test_helpers.py +903 -0
- tests/regscale/integrations/commercial/wizv2/core/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/core/test_auth.py +701 -0
- tests/regscale/integrations/commercial/wizv2/core/test_client.py +1037 -0
- tests/regscale/integrations/commercial/wizv2/core/test_file_operations.py +989 -0
- tests/regscale/integrations/commercial/wizv2/fetchers/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/fetchers/test_policy_assessment.py +805 -0
- tests/regscale/integrations/commercial/wizv2/parsers/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/parsers/test_main.py +1153 -0
- tests/regscale/integrations/commercial/wizv2/processors/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/processors/test_finding.py +671 -0
- tests/regscale/integrations/commercial/wizv2/test_WizDataMixin.py +537 -0
- tests/regscale/integrations/commercial/wizv2/test_click_comprehensive.py +851 -0
- tests/regscale/integrations/commercial/wizv2/test_compliance_report_comprehensive.py +910 -0
- tests/regscale/integrations/commercial/wizv2/test_compliance_report_normalization.py +138 -0
- tests/regscale/integrations/commercial/wizv2/test_file_cleanup.py +283 -0
- tests/regscale/integrations/commercial/wizv2/test_file_operations.py +260 -0
- tests/regscale/integrations/commercial/wizv2/test_issue.py +343 -0
- tests/regscale/integrations/commercial/wizv2/test_issue_comprehensive.py +1203 -0
- tests/regscale/integrations/commercial/wizv2/test_reports.py +497 -0
- tests/regscale/integrations/commercial/wizv2/test_sbom.py +643 -0
- tests/regscale/integrations/commercial/wizv2/test_scanner_comprehensive.py +805 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_click_client_id.py +165 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_report.py +1394 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_unit.py +341 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_control_normalization.py +138 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_findings_comprehensive.py +364 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_inventory_comprehensive.py +644 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_status_mapping.py +149 -0
- tests/regscale/integrations/commercial/wizv2/test_wizv2.py +1218 -0
- tests/regscale/integrations/commercial/wizv2/test_wizv2_utils.py +519 -0
- tests/regscale/integrations/commercial/wizv2/utils/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/utils/test_main.py +1523 -0
- tests/regscale/integrations/public/__init__.py +0 -0
- tests/regscale/integrations/public/fedramp/__init__.py +1 -0
- tests/regscale/integrations/public/fedramp/test_gen_asset_list.py +150 -0
- tests/regscale/integrations/public/fedramp/test_poam_export_v5.py +1293 -0
- tests/regscale/integrations/public/test_alienvault.py +220 -0
- tests/regscale/integrations/public/test_cci.py +1053 -0
- tests/regscale/integrations/public/test_cisa.py +1021 -0
- tests/regscale/integrations/public/test_emass.py +518 -0
- tests/regscale/integrations/public/test_fedramp.py +1152 -0
- tests/regscale/integrations/public/test_fedramp_cis_crm.py +3661 -0
- tests/regscale/integrations/public/test_file_uploads.py +506 -0
- tests/regscale/integrations/public/test_oscal.py +453 -0
- tests/regscale/integrations/test_compliance_status_mapping.py +406 -0
- tests/regscale/integrations/test_control_matcher.py +1421 -0
- tests/regscale/integrations/test_control_matching.py +155 -0
- tests/regscale/integrations/test_milestone_manager.py +408 -0
- tests/regscale/models/test_control_implementation.py +118 -3
- tests/regscale/models/test_form_field_value_integration.py +304 -0
- tests/regscale/models/test_issue.py +378 -1
- tests/regscale/models/test_module_integration.py +582 -0
- tests/regscale/models/test_tenable_integrations.py +811 -105
- regscale/integrations/commercial/wizv2/policy_compliance.py +0 -3057
- regscale/integrations/public/fedramp/mappings/fedramp_r4_parts.json +0 -7388
- regscale/integrations/public/fedramp/mappings/fedramp_r5_parts.json +0 -9605
- regscale/integrations/public/fedramp/parts_mapper.py +0 -107
- /regscale/integrations/commercial/{amazon → sarif}/__init__.py +0 -0
- /regscale/integrations/commercial/wizv2/{wiz_auth.py → core/auth.py} +0 -0
- {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/LICENSE +0 -0
- {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/WHEEL +0 -0
- {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,632 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""AWS S3 Evidence Integration for RegScale Compliance."""
|
|
4
|
+
|
|
5
|
+
import gzip
|
|
6
|
+
import json
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
import tempfile
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from datetime import datetime, timedelta
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any, Dict, List, Optional
|
|
14
|
+
|
|
15
|
+
import boto3
|
|
16
|
+
|
|
17
|
+
from regscale.core.app.api import Api
|
|
18
|
+
from regscale.integrations.commercial.aws.inventory.resources.s3 import S3Collector
|
|
19
|
+
from regscale.integrations.commercial.aws.s3_control_mappings import S3ControlMapper
|
|
20
|
+
from regscale.integrations.compliance_integration import ComplianceIntegration
|
|
21
|
+
from regscale.models.regscale_models.evidence import Evidence
|
|
22
|
+
from regscale.models.regscale_models.evidence_mapping import EvidenceMapping
|
|
23
|
+
from regscale.models.regscale_models.file import File
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger("regscale")
|
|
26
|
+
|
|
27
|
+
# HTML formatting constants
|
|
28
|
+
HTML_UL_CLOSE = "</ul>"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class S3EvidenceConfig:
|
|
33
|
+
"""Configuration for AWS S3 evidence collection."""
|
|
34
|
+
|
|
35
|
+
plan_id: int
|
|
36
|
+
region: str = "us-east-1"
|
|
37
|
+
framework: str = "NIST800-53R5"
|
|
38
|
+
create_issues: bool = False
|
|
39
|
+
update_control_status: bool = True
|
|
40
|
+
create_poams: bool = False
|
|
41
|
+
parent_module: str = "securityplans"
|
|
42
|
+
account_id: Optional[str] = None
|
|
43
|
+
tags: Optional[Dict[str, str]] = None
|
|
44
|
+
bucket_name_filter: Optional[str] = None
|
|
45
|
+
create_evidence: bool = False
|
|
46
|
+
create_ssp_attachment: bool = True
|
|
47
|
+
evidence_control_ids: Optional[List[str]] = None
|
|
48
|
+
force_refresh: bool = False
|
|
49
|
+
aws_profile: Optional[str] = None
|
|
50
|
+
aws_access_key_id: Optional[str] = None
|
|
51
|
+
aws_secret_access_key: Optional[str] = None
|
|
52
|
+
aws_session_token: Optional[str] = None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class S3ComplianceItem:
|
|
56
|
+
"""Represents S3 bucket configuration for compliance assessment."""
|
|
57
|
+
|
|
58
|
+
def __init__(self, bucket_data: Dict[str, Any]):
|
|
59
|
+
"""
|
|
60
|
+
Initialize S3 compliance item from bucket data.
|
|
61
|
+
|
|
62
|
+
:param Dict bucket_data: Bucket configuration data from S3Collector
|
|
63
|
+
"""
|
|
64
|
+
self.bucket_name = bucket_data.get("Name", "")
|
|
65
|
+
self.region = bucket_data.get("Region", "")
|
|
66
|
+
self.creation_date = bucket_data.get("CreationDate", "")
|
|
67
|
+
self.encryption = bucket_data.get("Encryption", {})
|
|
68
|
+
self.versioning = bucket_data.get("Versioning", {})
|
|
69
|
+
self.public_access_block = bucket_data.get("PublicAccessBlock", {})
|
|
70
|
+
self.policy_status = bucket_data.get("PolicyStatus", {})
|
|
71
|
+
self.acl = bucket_data.get("ACL", {})
|
|
72
|
+
self.logging = bucket_data.get("Logging", {})
|
|
73
|
+
self.tags = bucket_data.get("Tags", [])
|
|
74
|
+
self.raw_data = bucket_data
|
|
75
|
+
|
|
76
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
77
|
+
"""Convert to dictionary representation."""
|
|
78
|
+
return self.raw_data
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class AWSS3EvidenceIntegration(ComplianceIntegration):
|
|
82
|
+
"""AWS S3 evidence integration for compliance data collection."""
|
|
83
|
+
|
|
84
|
+
def __init__(self, config: S3EvidenceConfig):
|
|
85
|
+
"""
|
|
86
|
+
Initialize AWS S3 evidence integration.
|
|
87
|
+
|
|
88
|
+
:param S3EvidenceConfig config: Configuration object containing all parameters
|
|
89
|
+
"""
|
|
90
|
+
super().__init__(
|
|
91
|
+
plan_id=config.plan_id,
|
|
92
|
+
framework=config.framework,
|
|
93
|
+
create_issues=config.create_issues,
|
|
94
|
+
update_control_status=config.update_control_status,
|
|
95
|
+
create_poams=config.create_poams,
|
|
96
|
+
parent_module=config.parent_module,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
self.plan_id = config.plan_id
|
|
100
|
+
self.region = config.region
|
|
101
|
+
self.title = "AWS S3"
|
|
102
|
+
self.account_id = config.account_id
|
|
103
|
+
self.tags = config.tags or {}
|
|
104
|
+
self.bucket_name_filter = config.bucket_name_filter
|
|
105
|
+
self.create_evidence = config.create_evidence
|
|
106
|
+
self.create_ssp_attachment = config.create_ssp_attachment
|
|
107
|
+
self.evidence_control_ids = config.evidence_control_ids or []
|
|
108
|
+
self.force_refresh = config.force_refresh
|
|
109
|
+
|
|
110
|
+
# Initialize control mapper
|
|
111
|
+
self.control_mapper = S3ControlMapper(framework=config.framework)
|
|
112
|
+
|
|
113
|
+
# AWS credentials
|
|
114
|
+
self.aws_profile = config.aws_profile
|
|
115
|
+
self.aws_access_key_id = config.aws_access_key_id
|
|
116
|
+
self.aws_secret_access_key = config.aws_secret_access_key
|
|
117
|
+
self.aws_session_token = config.aws_session_token
|
|
118
|
+
|
|
119
|
+
# Initialize components
|
|
120
|
+
self.api = Api()
|
|
121
|
+
self.session = None
|
|
122
|
+
self.collector = None
|
|
123
|
+
|
|
124
|
+
# Cache configuration
|
|
125
|
+
self.cache_ttl_hours = 4
|
|
126
|
+
self.cache_dir = Path(tempfile.gettempdir()) / "regscale" / "aws_s3_cache"
|
|
127
|
+
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
|
128
|
+
|
|
129
|
+
# Data storage
|
|
130
|
+
self.raw_s3_data: Dict[str, Any] = {}
|
|
131
|
+
self.buckets: List[S3ComplianceItem] = []
|
|
132
|
+
|
|
133
|
+
def _get_cache_file_path(self) -> Path:
|
|
134
|
+
"""Get cache file path for S3 data."""
|
|
135
|
+
cache_key = f"{self.region}_{self.account_id or 'default'}"
|
|
136
|
+
return self.cache_dir / f"s3_buckets_{cache_key}.json"
|
|
137
|
+
|
|
138
|
+
def _is_cache_valid(self) -> bool:
|
|
139
|
+
"""Check if cache is valid and not expired."""
|
|
140
|
+
cache_file = self._get_cache_file_path()
|
|
141
|
+
if not cache_file.exists():
|
|
142
|
+
return False
|
|
143
|
+
|
|
144
|
+
cache_age = datetime.now() - datetime.fromtimestamp(cache_file.stat().st_mtime)
|
|
145
|
+
return cache_age < timedelta(hours=self.cache_ttl_hours)
|
|
146
|
+
|
|
147
|
+
def _save_cache(self, data: Dict[str, Any]) -> None:
|
|
148
|
+
"""Save S3 data to cache."""
|
|
149
|
+
cache_file = self._get_cache_file_path()
|
|
150
|
+
try:
|
|
151
|
+
with open(cache_file, "w", encoding="utf-8") as f:
|
|
152
|
+
json.dump(data, f, default=str)
|
|
153
|
+
logger.debug(f"Saved S3 data to cache: {cache_file}")
|
|
154
|
+
except Exception as e:
|
|
155
|
+
logger.warning(f"Failed to save cache: {e}")
|
|
156
|
+
|
|
157
|
+
def _load_cached_data(self) -> Optional[List[Dict[str, Any]]]:
|
|
158
|
+
"""Load S3 data from cache."""
|
|
159
|
+
cache_file = self._get_cache_file_path()
|
|
160
|
+
try:
|
|
161
|
+
with open(cache_file, encoding="utf-8") as f:
|
|
162
|
+
data = json.load(f)
|
|
163
|
+
|
|
164
|
+
# Validate cache format - must be a list of dicts
|
|
165
|
+
if not isinstance(data, list):
|
|
166
|
+
logger.warning("Invalid cache format detected (not a list). Invalidating cache.")
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
# Check if first item is a dict (bucket configuration)
|
|
170
|
+
if data and not isinstance(data[0], dict):
|
|
171
|
+
logger.warning("Invalid cache format detected (items not dicts). Invalidating cache.")
|
|
172
|
+
return None
|
|
173
|
+
|
|
174
|
+
logger.info(f"Loaded S3 data from cache (age: {self._get_cache_age_hours():.1f} hours)")
|
|
175
|
+
return data
|
|
176
|
+
except Exception as e:
|
|
177
|
+
logger.warning(f"Failed to load cache: {e}")
|
|
178
|
+
return None
|
|
179
|
+
|
|
180
|
+
def _get_cache_age_hours(self) -> float:
|
|
181
|
+
"""Get cache age in hours."""
|
|
182
|
+
cache_file = self._get_cache_file_path()
|
|
183
|
+
if not cache_file.exists():
|
|
184
|
+
return float("inf")
|
|
185
|
+
cache_age = datetime.now() - datetime.fromtimestamp(cache_file.stat().st_mtime)
|
|
186
|
+
return cache_age.total_seconds() / 3600
|
|
187
|
+
|
|
188
|
+
def _initialize_aws_session(self) -> None:
|
|
189
|
+
"""Initialize AWS session using provided credentials."""
|
|
190
|
+
if self.aws_access_key_id and self.aws_secret_access_key:
|
|
191
|
+
self.session = boto3.Session(
|
|
192
|
+
aws_access_key_id=self.aws_access_key_id,
|
|
193
|
+
aws_secret_access_key=self.aws_secret_access_key,
|
|
194
|
+
aws_session_token=self.aws_session_token,
|
|
195
|
+
region_name=self.region,
|
|
196
|
+
)
|
|
197
|
+
elif self.aws_profile:
|
|
198
|
+
self.session = boto3.Session(profile_name=self.aws_profile, region_name=self.region)
|
|
199
|
+
else:
|
|
200
|
+
self.session = boto3.Session(region_name=self.region)
|
|
201
|
+
logger.info(f"Initialized AWS session for region: {self.region}")
|
|
202
|
+
|
|
203
|
+
def fetch_compliance_data(self) -> List[Dict[str, Any]]:
|
|
204
|
+
"""
|
|
205
|
+
Fetch S3 bucket configuration data from AWS.
|
|
206
|
+
|
|
207
|
+
:return: List of bucket configurations
|
|
208
|
+
:rtype: List[Dict[str, Any]]
|
|
209
|
+
"""
|
|
210
|
+
# Check cache first
|
|
211
|
+
if not self.force_refresh and self._is_cache_valid():
|
|
212
|
+
cached_data = self._load_cached_data()
|
|
213
|
+
if cached_data:
|
|
214
|
+
return cached_data
|
|
215
|
+
|
|
216
|
+
# Fetch fresh data
|
|
217
|
+
return self._fetch_fresh_s3_data()
|
|
218
|
+
|
|
219
|
+
def _fetch_fresh_s3_data(self) -> List[Dict[str, Any]]:
|
|
220
|
+
"""
|
|
221
|
+
Fetch fresh S3 bucket data from AWS API.
|
|
222
|
+
|
|
223
|
+
:return: List of bucket configurations
|
|
224
|
+
:rtype: List[Dict[str, Any]]
|
|
225
|
+
"""
|
|
226
|
+
logger.info(f"Fetching S3 bucket configurations from AWS region: {self.region}")
|
|
227
|
+
|
|
228
|
+
# Initialize AWS session
|
|
229
|
+
if not self.session:
|
|
230
|
+
self._initialize_aws_session()
|
|
231
|
+
|
|
232
|
+
# Create S3 collector
|
|
233
|
+
self.collector = S3Collector(
|
|
234
|
+
session=self.session, region=self.region, account_id=self.account_id, tags=self.tags
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
# Collect S3 data
|
|
238
|
+
self.raw_s3_data = self.collector.collect()
|
|
239
|
+
buckets = self.raw_s3_data.get("Buckets", [])
|
|
240
|
+
|
|
241
|
+
# Apply bucket name filter if specified
|
|
242
|
+
if self.bucket_name_filter:
|
|
243
|
+
buckets = [b for b in buckets if self.bucket_name_filter in b.get("Name", "")]
|
|
244
|
+
logger.info(f"Applied bucket name filter '{self.bucket_name_filter}': {len(buckets)} buckets match")
|
|
245
|
+
|
|
246
|
+
logger.info(f"Collected {len(buckets)} S3 bucket(s) from region {self.region}")
|
|
247
|
+
|
|
248
|
+
# Save to cache
|
|
249
|
+
self._save_cache(buckets)
|
|
250
|
+
|
|
251
|
+
return buckets
|
|
252
|
+
|
|
253
|
+
def sync_compliance_data(self) -> None:
|
|
254
|
+
"""Sync S3 compliance data to RegScale."""
|
|
255
|
+
logger.info("Starting AWS S3 compliance data sync to RegScale")
|
|
256
|
+
|
|
257
|
+
# Fetch bucket data
|
|
258
|
+
bucket_data = self.fetch_compliance_data()
|
|
259
|
+
if not bucket_data:
|
|
260
|
+
logger.warning("No S3 bucket data to sync")
|
|
261
|
+
return
|
|
262
|
+
|
|
263
|
+
# Convert to compliance items
|
|
264
|
+
self.buckets = [S3ComplianceItem(bucket) for bucket in bucket_data]
|
|
265
|
+
logger.info(f"Processing {len(self.buckets)} S3 bucket(s) for compliance assessment")
|
|
266
|
+
|
|
267
|
+
# Assess compliance
|
|
268
|
+
compliance_results = self._assess_compliance()
|
|
269
|
+
|
|
270
|
+
# Populate control dictionaries for assessment creation
|
|
271
|
+
if self.update_control_status:
|
|
272
|
+
self._populate_control_results(compliance_results["overall"])
|
|
273
|
+
# Create control assessments and update implementation statuses
|
|
274
|
+
self._process_control_assessments()
|
|
275
|
+
|
|
276
|
+
# Create evidence artifacts
|
|
277
|
+
if self.create_evidence or self.create_ssp_attachment:
|
|
278
|
+
self._create_evidence_artifacts(compliance_results)
|
|
279
|
+
|
|
280
|
+
logger.info("AWS S3 compliance sync completed successfully")
|
|
281
|
+
|
|
282
|
+
def create_compliance_item(self, raw_data: Dict[str, Any]):
|
|
283
|
+
"""
|
|
284
|
+
Create a ComplianceItem from raw S3 bucket data.
|
|
285
|
+
|
|
286
|
+
:param Dict[str, Any] raw_data: Raw S3 bucket data
|
|
287
|
+
:return: S3ComplianceItem instance
|
|
288
|
+
:rtype: S3ComplianceItem
|
|
289
|
+
"""
|
|
290
|
+
return S3ComplianceItem(raw_data)
|
|
291
|
+
|
|
292
|
+
def _assess_compliance(self) -> Dict[str, Any]:
|
|
293
|
+
"""
|
|
294
|
+
Assess S3 compliance against NIST controls.
|
|
295
|
+
|
|
296
|
+
:return: Compliance assessment results
|
|
297
|
+
:rtype: Dict[str, Any]
|
|
298
|
+
"""
|
|
299
|
+
logger.info("Assessing S3 compliance against NIST 800-53 R5 controls")
|
|
300
|
+
|
|
301
|
+
# Assess each bucket individually
|
|
302
|
+
bucket_assessments = []
|
|
303
|
+
for bucket_item in self.buckets:
|
|
304
|
+
bucket_result = self.control_mapper.assess_bucket_compliance(bucket_item.to_dict())
|
|
305
|
+
bucket_assessments.append({"bucket_name": bucket_item.bucket_name, "controls": bucket_result})
|
|
306
|
+
|
|
307
|
+
# Get overall compliance results
|
|
308
|
+
bucket_dicts = [b.to_dict() for b in self.buckets]
|
|
309
|
+
overall_results = self.control_mapper.assess_all_buckets_compliance(bucket_dicts)
|
|
310
|
+
|
|
311
|
+
# Log summary
|
|
312
|
+
passed_controls = [ctrl for ctrl, result in overall_results.items() if result == "PASS"]
|
|
313
|
+
failed_controls = [ctrl for ctrl, result in overall_results.items() if result == "FAIL"]
|
|
314
|
+
|
|
315
|
+
logger.info("S3 Compliance Assessment Summary:")
|
|
316
|
+
logger.info(f" Total Buckets: {len(self.buckets)}")
|
|
317
|
+
logger.info(f" Controls Passed: {len(passed_controls)} - {', '.join(passed_controls)}")
|
|
318
|
+
logger.info(f" Controls Failed: {len(failed_controls)} - {', '.join(failed_controls)}")
|
|
319
|
+
|
|
320
|
+
return {"overall": overall_results, "buckets": bucket_assessments}
|
|
321
|
+
|
|
322
|
+
def _populate_control_results(self, control_results: Dict[str, str]) -> None:
|
|
323
|
+
"""
|
|
324
|
+
Populate passing_controls and failing_controls dictionaries from assessment results.
|
|
325
|
+
|
|
326
|
+
This method converts the control-level assessment results into the format expected
|
|
327
|
+
by the base class _process_control_assessments() method.
|
|
328
|
+
|
|
329
|
+
:param Dict[str, str] control_results: Control assessment results (e.g., {"AC-2": "PASS", "AC-3": "FAIL"})
|
|
330
|
+
:return: None
|
|
331
|
+
:rtype: None
|
|
332
|
+
"""
|
|
333
|
+
for control_id, result in control_results.items():
|
|
334
|
+
# Normalize control ID to lowercase for consistent lookup
|
|
335
|
+
control_key = control_id.lower()
|
|
336
|
+
|
|
337
|
+
# Use first bucket as placeholder for the base class
|
|
338
|
+
placeholder_item = self.buckets[0] if self.buckets else None
|
|
339
|
+
if not placeholder_item:
|
|
340
|
+
continue
|
|
341
|
+
|
|
342
|
+
# Create a simple compliance item placeholder for the base class
|
|
343
|
+
if result in self.PASS_STATUSES:
|
|
344
|
+
self.passing_controls[control_key] = placeholder_item
|
|
345
|
+
elif result in self.FAIL_STATUSES:
|
|
346
|
+
self.failing_controls[control_key] = placeholder_item
|
|
347
|
+
|
|
348
|
+
logger.debug(
|
|
349
|
+
f"Populated control results: {len(self.passing_controls)} passing, {len(self.failing_controls)} failing"
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
def _create_evidence_artifacts(self, compliance_results: Dict[str, Any]) -> None:
|
|
353
|
+
"""
|
|
354
|
+
Create evidence artifacts in RegScale.
|
|
355
|
+
|
|
356
|
+
:param Dict compliance_results: Compliance assessment results
|
|
357
|
+
"""
|
|
358
|
+
logger.info("Creating S3 evidence artifacts in RegScale")
|
|
359
|
+
|
|
360
|
+
# Create comprehensive evidence file
|
|
361
|
+
evidence_file_path = self._create_evidence_file(compliance_results)
|
|
362
|
+
|
|
363
|
+
if self.create_ssp_attachment:
|
|
364
|
+
self._create_ssp_attachment_with_evidence(evidence_file_path)
|
|
365
|
+
|
|
366
|
+
if self.create_evidence:
|
|
367
|
+
self._create_evidence_records(evidence_file_path, compliance_results)
|
|
368
|
+
|
|
369
|
+
# Clean up temporary file
|
|
370
|
+
if os.path.exists(evidence_file_path):
|
|
371
|
+
os.remove(evidence_file_path)
|
|
372
|
+
logger.debug(f"Cleaned up temporary evidence file: {evidence_file_path}")
|
|
373
|
+
|
|
374
|
+
def _create_evidence_file(self, compliance_results: Dict[str, Any]) -> str:
|
|
375
|
+
"""
|
|
376
|
+
Create JSONL.GZ evidence file with S3 configuration data.
|
|
377
|
+
|
|
378
|
+
:param Dict compliance_results: Compliance assessment results
|
|
379
|
+
:return: Path to created evidence file
|
|
380
|
+
:rtype: str
|
|
381
|
+
"""
|
|
382
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
383
|
+
evidence_file = os.path.join(tempfile.gettempdir(), f"s3_evidence_{self.region}_{timestamp}.jsonl.gz")
|
|
384
|
+
|
|
385
|
+
try:
|
|
386
|
+
with gzip.open(evidence_file, "wt", encoding="utf-8") as f:
|
|
387
|
+
# Write metadata
|
|
388
|
+
metadata = {
|
|
389
|
+
"type": "metadata",
|
|
390
|
+
"timestamp": datetime.now().isoformat(),
|
|
391
|
+
"region": self.region,
|
|
392
|
+
"account_id": self.account_id,
|
|
393
|
+
"bucket_count": len(self.buckets),
|
|
394
|
+
"compliance_framework": "NIST800-53R5",
|
|
395
|
+
}
|
|
396
|
+
f.write(json.dumps(metadata) + "\n")
|
|
397
|
+
|
|
398
|
+
# Write compliance summary
|
|
399
|
+
summary = {"type": "compliance_summary", "results": compliance_results["overall"]}
|
|
400
|
+
f.write(json.dumps(summary) + "\n")
|
|
401
|
+
|
|
402
|
+
# Write bucket configurations
|
|
403
|
+
for bucket_item in self.buckets:
|
|
404
|
+
bucket_record = {
|
|
405
|
+
"type": "bucket_configuration",
|
|
406
|
+
"bucket_name": bucket_item.bucket_name,
|
|
407
|
+
"region": bucket_item.region,
|
|
408
|
+
"encryption": bucket_item.encryption,
|
|
409
|
+
"versioning": bucket_item.versioning,
|
|
410
|
+
"public_access_block": bucket_item.public_access_block,
|
|
411
|
+
"policy_status": bucket_item.policy_status,
|
|
412
|
+
"acl": bucket_item.acl,
|
|
413
|
+
"logging": bucket_item.logging,
|
|
414
|
+
"tags": bucket_item.tags,
|
|
415
|
+
}
|
|
416
|
+
f.write(json.dumps(bucket_record, default=str) + "\n")
|
|
417
|
+
|
|
418
|
+
logger.info(f"Created evidence file: {evidence_file}")
|
|
419
|
+
return evidence_file
|
|
420
|
+
|
|
421
|
+
except Exception as e:
|
|
422
|
+
logger.error(f"Failed to create evidence file: {e}", exc_info=True)
|
|
423
|
+
raise
|
|
424
|
+
|
|
425
|
+
def _create_ssp_attachment_with_evidence(self, evidence_file_path: str) -> None:
|
|
426
|
+
"""
|
|
427
|
+
Create SSP attachment with S3 evidence.
|
|
428
|
+
|
|
429
|
+
:param str evidence_file_path: Path to evidence file
|
|
430
|
+
"""
|
|
431
|
+
try:
|
|
432
|
+
date_str = datetime.now().strftime("%Y%m%d")
|
|
433
|
+
file_name_pattern = f"s3_evidence_{self.region}_{date_str}"
|
|
434
|
+
|
|
435
|
+
# Check if evidence for today already exists using base class method
|
|
436
|
+
if self.check_for_existing_evidence(file_name_pattern):
|
|
437
|
+
logger.info(
|
|
438
|
+
f"Evidence file for S3 in region {self.region} already exists for today. "
|
|
439
|
+
"Skipping upload to avoid duplicates."
|
|
440
|
+
)
|
|
441
|
+
return
|
|
442
|
+
|
|
443
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
444
|
+
file_name = f"s3_evidence_{self.region}_{timestamp}.jsonl.gz"
|
|
445
|
+
|
|
446
|
+
# Read the compressed file
|
|
447
|
+
with open(evidence_file_path, "rb") as f:
|
|
448
|
+
file_data = f.read()
|
|
449
|
+
|
|
450
|
+
# Upload file to RegScale
|
|
451
|
+
success = File.upload_file_to_regscale(
|
|
452
|
+
file_name=file_name,
|
|
453
|
+
parent_id=self.plan_id,
|
|
454
|
+
parent_module="securityplans",
|
|
455
|
+
api=self.api,
|
|
456
|
+
file_data=file_data,
|
|
457
|
+
tags="aws,s3,storage,compliance,automated",
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
if success:
|
|
461
|
+
logger.info(f"Successfully uploaded S3 evidence file: {file_name}")
|
|
462
|
+
else:
|
|
463
|
+
logger.error("Failed to upload S3 evidence file")
|
|
464
|
+
|
|
465
|
+
except Exception as e:
|
|
466
|
+
logger.error(f"Failed to create SSP attachment: {e}", exc_info=True)
|
|
467
|
+
|
|
468
|
+
def _create_evidence_records(self, evidence_file_path: str, compliance_results: Dict[str, Any]) -> None:
|
|
469
|
+
"""
|
|
470
|
+
Create evidence records in RegScale.
|
|
471
|
+
|
|
472
|
+
:param str evidence_file_path: Path to evidence file
|
|
473
|
+
:param Dict compliance_results: Compliance assessment results
|
|
474
|
+
"""
|
|
475
|
+
try:
|
|
476
|
+
description = self._build_evidence_description(compliance_results)
|
|
477
|
+
|
|
478
|
+
# Calculate due date (30 days from now as default)
|
|
479
|
+
due_date = (datetime.now() + timedelta(days=30)).isoformat()
|
|
480
|
+
|
|
481
|
+
# Create evidence record using Evidence model directly
|
|
482
|
+
evidence = Evidence(
|
|
483
|
+
title=f"AWS S3 Compliance Evidence - {self.region}",
|
|
484
|
+
description=description,
|
|
485
|
+
status="Collected",
|
|
486
|
+
updateFrequency=30,
|
|
487
|
+
dueDate=due_date,
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
created_evidence = evidence.create()
|
|
491
|
+
if not created_evidence or not created_evidence.id:
|
|
492
|
+
logger.error("Failed to create evidence record")
|
|
493
|
+
return
|
|
494
|
+
|
|
495
|
+
logger.info(f"Created evidence record: {created_evidence.id}")
|
|
496
|
+
|
|
497
|
+
# Upload evidence file
|
|
498
|
+
self._upload_evidence_file(created_evidence.id, evidence_file_path)
|
|
499
|
+
|
|
500
|
+
# Link evidence to SSP
|
|
501
|
+
self._link_evidence_to_ssp(created_evidence.id)
|
|
502
|
+
|
|
503
|
+
# Link to controls if specified
|
|
504
|
+
if self.evidence_control_ids:
|
|
505
|
+
self._link_evidence_to_controls(created_evidence.id, is_attachment=False)
|
|
506
|
+
|
|
507
|
+
except Exception as e:
|
|
508
|
+
logger.error(f"Failed to create evidence record: {e}", exc_info=True)
|
|
509
|
+
|
|
510
|
+
def _upload_evidence_file(self, evidence_id: int, file_path: str) -> None:
|
|
511
|
+
"""
|
|
512
|
+
Upload evidence file to RegScale.
|
|
513
|
+
|
|
514
|
+
:param int evidence_id: Evidence record ID
|
|
515
|
+
:param str file_path: Path to evidence file
|
|
516
|
+
"""
|
|
517
|
+
try:
|
|
518
|
+
# Read the compressed file
|
|
519
|
+
with open(file_path, "rb") as f:
|
|
520
|
+
file_data = f.read()
|
|
521
|
+
|
|
522
|
+
# Generate filename from the path
|
|
523
|
+
file_name = os.path.basename(file_path)
|
|
524
|
+
|
|
525
|
+
# Upload file to Evidence record
|
|
526
|
+
success = File.upload_file_to_regscale(
|
|
527
|
+
file_name=file_name,
|
|
528
|
+
parent_id=evidence_id,
|
|
529
|
+
parent_module="evidence",
|
|
530
|
+
api=self.api,
|
|
531
|
+
file_data=file_data,
|
|
532
|
+
tags="aws,s3,storage,compliance,automated",
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
if success:
|
|
536
|
+
logger.info(f"Uploaded evidence file for evidence ID: {evidence_id}")
|
|
537
|
+
else:
|
|
538
|
+
logger.warning(f"Failed to upload evidence file for evidence ID: {evidence_id}")
|
|
539
|
+
except Exception as e:
|
|
540
|
+
logger.error(f"Failed to upload evidence file: {e}", exc_info=True)
|
|
541
|
+
|
|
542
|
+
def _link_evidence_to_ssp(self, evidence_id: int) -> None:
|
|
543
|
+
"""
|
|
544
|
+
Link evidence to Security Plan.
|
|
545
|
+
|
|
546
|
+
:param int evidence_id: Evidence record ID
|
|
547
|
+
:return: None
|
|
548
|
+
:rtype: None
|
|
549
|
+
"""
|
|
550
|
+
try:
|
|
551
|
+
mapping = EvidenceMapping(evidenceID=evidence_id, mappedID=self.plan_id, mappingType=self.parent_module)
|
|
552
|
+
mapping.create()
|
|
553
|
+
logger.info(f"Linked evidence {evidence_id} to SSP {self.plan_id}")
|
|
554
|
+
except Exception as ex:
|
|
555
|
+
logger.warning(f"Failed to link evidence to SSP: {ex}")
|
|
556
|
+
|
|
557
|
+
def _link_evidence_to_controls(self, evidence_id: int, is_attachment: bool = False) -> None:
|
|
558
|
+
"""
|
|
559
|
+
Link evidence to specified control IDs.
|
|
560
|
+
|
|
561
|
+
:param int evidence_id: Evidence or attachment ID
|
|
562
|
+
:param bool is_attachment: True if linking attachment, False for evidence record
|
|
563
|
+
"""
|
|
564
|
+
try:
|
|
565
|
+
for control_id in self.evidence_control_ids:
|
|
566
|
+
if is_attachment:
|
|
567
|
+
self.api.link_ssp_attachment_to_control(self.plan_id, evidence_id, control_id)
|
|
568
|
+
else:
|
|
569
|
+
self.api.link_evidence_to_control(evidence_id, control_id)
|
|
570
|
+
logger.info(f"Linked evidence {evidence_id} to control {control_id}")
|
|
571
|
+
except Exception as e:
|
|
572
|
+
logger.error(f"Failed to link evidence to controls: {e}", exc_info=True)
|
|
573
|
+
|
|
574
|
+
def _build_evidence_description(self, compliance_results: Dict[str, Any]) -> str:
|
|
575
|
+
"""
|
|
576
|
+
Build HTML-formatted evidence description.
|
|
577
|
+
|
|
578
|
+
:param Dict compliance_results: Compliance assessment results
|
|
579
|
+
:return: HTML description
|
|
580
|
+
:rtype: str
|
|
581
|
+
"""
|
|
582
|
+
overall_results = compliance_results.get("overall", {})
|
|
583
|
+
passed_controls = [ctrl for ctrl, result in overall_results.items() if result == "PASS"]
|
|
584
|
+
failed_controls = [ctrl for ctrl, result in overall_results.items() if result == "FAIL"]
|
|
585
|
+
|
|
586
|
+
desc_parts = [
|
|
587
|
+
"<h3>AWS S3 Storage Configuration Evidence</h3>",
|
|
588
|
+
f"<p><strong>Region:</strong> {self.region}</p>",
|
|
589
|
+
f"<p><strong>Account ID:</strong> {self.account_id or 'N/A'}</p>",
|
|
590
|
+
f"<p><strong>Collection Date:</strong> {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>",
|
|
591
|
+
f"<p><strong>Total Buckets:</strong> {len(self.buckets)}</p>",
|
|
592
|
+
"<h4>Compliance Summary</h4>",
|
|
593
|
+
f"<p><strong>Controls Passed:</strong> {len(passed_controls)}</p>",
|
|
594
|
+
"<ul>",
|
|
595
|
+
]
|
|
596
|
+
|
|
597
|
+
for control in passed_controls:
|
|
598
|
+
control_desc = self.control_mapper.get_control_description(control)
|
|
599
|
+
desc_parts.append(f"<li>{control}: {control_desc}</li>")
|
|
600
|
+
|
|
601
|
+
desc_parts.append(HTML_UL_CLOSE)
|
|
602
|
+
|
|
603
|
+
if failed_controls:
|
|
604
|
+
desc_parts.append(f"<p><strong>Controls Failed:</strong> {len(failed_controls)}</p>")
|
|
605
|
+
desc_parts.append("<ul>")
|
|
606
|
+
for control in failed_controls:
|
|
607
|
+
control_desc = self.control_mapper.get_control_description(control)
|
|
608
|
+
desc_parts.append(f"<li>{control}: {control_desc}</li>")
|
|
609
|
+
desc_parts.append(HTML_UL_CLOSE)
|
|
610
|
+
|
|
611
|
+
desc_parts.extend(
|
|
612
|
+
[
|
|
613
|
+
"<h4>Bucket Configurations</h4>",
|
|
614
|
+
"<ul>",
|
|
615
|
+
]
|
|
616
|
+
)
|
|
617
|
+
|
|
618
|
+
for bucket_item in self.buckets[:10]: # Limit to first 10 for description
|
|
619
|
+
encryption_status = "Enabled" if bucket_item.encryption.get("Enabled") else "Disabled"
|
|
620
|
+
versioning_status = bucket_item.versioning.get("Status", "Disabled")
|
|
621
|
+
desc_parts.append(
|
|
622
|
+
f"<li><strong>{bucket_item.bucket_name}</strong>: "
|
|
623
|
+
f"Encryption={encryption_status}, Versioning={versioning_status}</li>"
|
|
624
|
+
)
|
|
625
|
+
|
|
626
|
+
if len(self.buckets) > 10:
|
|
627
|
+
desc_parts.append(f"<li><em>... and {len(self.buckets) - 10} more buckets</em></li>")
|
|
628
|
+
|
|
629
|
+
desc_parts.append(HTML_UL_CLOSE)
|
|
630
|
+
desc_parts.append("<p><em>Complete configuration data available in attached evidence file.</em></p>")
|
|
631
|
+
|
|
632
|
+
return "".join(desc_parts)
|