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,492 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""AWS Systems Manager 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.systems_manager import SystemsManagerCollector
|
|
19
|
+
from regscale.integrations.commercial.aws.ssm_control_mappings import SSMControlMapper
|
|
20
|
+
from regscale.integrations.compliance_integration import ComplianceIntegration
|
|
21
|
+
from regscale.models.regscale_models.file import File
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger("regscale")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class SSMEvidenceConfig:
|
|
28
|
+
"""Configuration for AWS Systems Manager evidence collection."""
|
|
29
|
+
|
|
30
|
+
plan_id: int
|
|
31
|
+
region: str = "us-east-1"
|
|
32
|
+
framework: str = "NIST800-53R5"
|
|
33
|
+
create_issues: bool = False
|
|
34
|
+
update_control_status: bool = True
|
|
35
|
+
create_poams: bool = False
|
|
36
|
+
parent_module: str = "securityplans"
|
|
37
|
+
account_id: Optional[str] = None
|
|
38
|
+
tags: Optional[Dict[str, str]] = None
|
|
39
|
+
create_evidence: bool = False
|
|
40
|
+
create_ssp_attachment: bool = True
|
|
41
|
+
evidence_control_ids: Optional[List[str]] = None
|
|
42
|
+
force_refresh: bool = False
|
|
43
|
+
aws_profile: Optional[str] = None
|
|
44
|
+
aws_access_key_id: Optional[str] = None
|
|
45
|
+
aws_secret_access_key: Optional[str] = None
|
|
46
|
+
aws_session_token: Optional[str] = None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class SSMComplianceItem:
|
|
50
|
+
"""Represents Systems Manager configuration for compliance assessment."""
|
|
51
|
+
|
|
52
|
+
def __init__(self, ssm_data: Dict[str, Any]):
|
|
53
|
+
"""
|
|
54
|
+
Initialize SSM compliance item from configuration data.
|
|
55
|
+
|
|
56
|
+
:param Dict ssm_data: SSM configuration data from SystemsManagerCollector
|
|
57
|
+
"""
|
|
58
|
+
self.managed_instances = ssm_data.get("ManagedInstances", [])
|
|
59
|
+
self.parameters = ssm_data.get("Parameters", [])
|
|
60
|
+
self.documents = ssm_data.get("Documents", [])
|
|
61
|
+
self.patch_baselines = ssm_data.get("PatchBaselines", [])
|
|
62
|
+
self.maintenance_windows = ssm_data.get("MaintenanceWindows", [])
|
|
63
|
+
self.associations = ssm_data.get("Associations", [])
|
|
64
|
+
self.inventory_entries = ssm_data.get("InventoryEntries", [])
|
|
65
|
+
self.compliance_summary = ssm_data.get("ComplianceSummary", {})
|
|
66
|
+
self.raw_data = ssm_data
|
|
67
|
+
|
|
68
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
69
|
+
"""Convert to dictionary representation."""
|
|
70
|
+
return self.raw_data
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class AWSSSMEvidenceIntegration(ComplianceIntegration):
|
|
74
|
+
"""AWS Systems Manager evidence integration for compliance data collection."""
|
|
75
|
+
|
|
76
|
+
def __init__(self, config: SSMEvidenceConfig):
|
|
77
|
+
"""
|
|
78
|
+
Initialize AWS Systems Manager evidence integration.
|
|
79
|
+
|
|
80
|
+
:param SSMEvidenceConfig config: Configuration object containing all parameters
|
|
81
|
+
"""
|
|
82
|
+
super().__init__(
|
|
83
|
+
plan_id=config.plan_id,
|
|
84
|
+
framework=config.framework,
|
|
85
|
+
create_issues=config.create_issues,
|
|
86
|
+
update_control_status=config.update_control_status,
|
|
87
|
+
create_poams=config.create_poams,
|
|
88
|
+
parent_module=config.parent_module,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
self.plan_id = config.plan_id
|
|
92
|
+
self.region = config.region
|
|
93
|
+
self.title = "AWS Systems Manager"
|
|
94
|
+
self.account_id = config.account_id
|
|
95
|
+
self.tags = config.tags or {}
|
|
96
|
+
self.create_evidence = config.create_evidence
|
|
97
|
+
self.create_ssp_attachment = config.create_ssp_attachment
|
|
98
|
+
self.evidence_control_ids = config.evidence_control_ids or []
|
|
99
|
+
self.force_refresh = config.force_refresh
|
|
100
|
+
|
|
101
|
+
# AWS credentials
|
|
102
|
+
self.aws_profile = config.aws_profile
|
|
103
|
+
self.aws_access_key_id = config.aws_access_key_id
|
|
104
|
+
self.aws_secret_access_key = config.aws_secret_access_key
|
|
105
|
+
self.aws_session_token = config.aws_session_token
|
|
106
|
+
|
|
107
|
+
# Initialize components
|
|
108
|
+
self.api = Api()
|
|
109
|
+
self.control_mapper = SSMControlMapper(framework=config.framework)
|
|
110
|
+
self.session = None
|
|
111
|
+
self.collector = None
|
|
112
|
+
|
|
113
|
+
# Cache configuration
|
|
114
|
+
self.cache_ttl_hours = 4
|
|
115
|
+
self.cache_dir = Path(tempfile.gettempdir()) / "regscale" / "aws_ssm_cache"
|
|
116
|
+
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
|
117
|
+
|
|
118
|
+
# Data storage
|
|
119
|
+
self.raw_ssm_data: Dict[str, Any] = {}
|
|
120
|
+
self.ssm_item: Optional[SSMComplianceItem] = None
|
|
121
|
+
|
|
122
|
+
def _get_cache_file_path(self) -> Path:
|
|
123
|
+
"""Get cache file path for SSM data."""
|
|
124
|
+
cache_key = f"{self.region}_{self.account_id or 'default'}"
|
|
125
|
+
return self.cache_dir / f"ssm_data_{cache_key}.json"
|
|
126
|
+
|
|
127
|
+
def _is_cache_valid(self) -> bool:
|
|
128
|
+
"""Check if cache is valid and not expired."""
|
|
129
|
+
cache_file = self._get_cache_file_path()
|
|
130
|
+
if not cache_file.exists():
|
|
131
|
+
return False
|
|
132
|
+
|
|
133
|
+
cache_age = datetime.now() - datetime.fromtimestamp(cache_file.stat().st_mtime)
|
|
134
|
+
return cache_age < timedelta(hours=self.cache_ttl_hours)
|
|
135
|
+
|
|
136
|
+
def _save_cache(self, data: Dict[str, Any]) -> None:
|
|
137
|
+
"""Save SSM data to cache."""
|
|
138
|
+
cache_file = self._get_cache_file_path()
|
|
139
|
+
try:
|
|
140
|
+
with open(cache_file, "w", encoding="utf-8") as f:
|
|
141
|
+
json.dump(data, f, default=str)
|
|
142
|
+
logger.debug(f"Saved SSM data to cache: {cache_file}")
|
|
143
|
+
except Exception as e:
|
|
144
|
+
logger.warning(f"Failed to save cache: {e}")
|
|
145
|
+
|
|
146
|
+
def _load_cached_data(self) -> Optional[Dict[str, Any]]:
|
|
147
|
+
"""Load SSM data from cache."""
|
|
148
|
+
cache_file = self._get_cache_file_path()
|
|
149
|
+
try:
|
|
150
|
+
with open(cache_file, encoding="utf-8") as f:
|
|
151
|
+
data = json.load(f)
|
|
152
|
+
|
|
153
|
+
# Validate cache format - must be a dict
|
|
154
|
+
if not isinstance(data, dict):
|
|
155
|
+
logger.warning("Invalid cache format detected (not a dict). Invalidating cache.")
|
|
156
|
+
return None
|
|
157
|
+
|
|
158
|
+
logger.info(f"Loaded SSM data from cache (age: {self._get_cache_age_hours():.1f} hours)")
|
|
159
|
+
return data
|
|
160
|
+
except Exception as e:
|
|
161
|
+
logger.warning(f"Failed to load cache: {e}")
|
|
162
|
+
return None
|
|
163
|
+
|
|
164
|
+
def _get_cache_age_hours(self) -> float:
|
|
165
|
+
"""Get cache age in hours."""
|
|
166
|
+
cache_file = self._get_cache_file_path()
|
|
167
|
+
if not cache_file.exists():
|
|
168
|
+
return float("inf")
|
|
169
|
+
cache_age = datetime.now() - datetime.fromtimestamp(cache_file.stat().st_mtime)
|
|
170
|
+
return cache_age.total_seconds() / 3600
|
|
171
|
+
|
|
172
|
+
def _initialize_aws_session(self) -> None:
|
|
173
|
+
"""Initialize AWS session using provided credentials."""
|
|
174
|
+
if self.aws_access_key_id and self.aws_secret_access_key:
|
|
175
|
+
self.session = boto3.Session(
|
|
176
|
+
aws_access_key_id=self.aws_access_key_id,
|
|
177
|
+
aws_secret_access_key=self.aws_secret_access_key,
|
|
178
|
+
aws_session_token=self.aws_session_token,
|
|
179
|
+
region_name=self.region,
|
|
180
|
+
)
|
|
181
|
+
elif self.aws_profile:
|
|
182
|
+
self.session = boto3.Session(profile_name=self.aws_profile, region_name=self.region)
|
|
183
|
+
else:
|
|
184
|
+
self.session = boto3.Session(region_name=self.region)
|
|
185
|
+
logger.info(f"Initialized AWS session for region: {self.region}")
|
|
186
|
+
|
|
187
|
+
def fetch_compliance_data(self) -> Dict[str, Any]:
|
|
188
|
+
"""
|
|
189
|
+
Fetch Systems Manager configuration data from AWS.
|
|
190
|
+
|
|
191
|
+
:return: SSM configuration data
|
|
192
|
+
:rtype: Dict[str, Any]
|
|
193
|
+
"""
|
|
194
|
+
# Check cache first
|
|
195
|
+
if not self.force_refresh and self._is_cache_valid():
|
|
196
|
+
cached_data = self._load_cached_data()
|
|
197
|
+
if cached_data:
|
|
198
|
+
return cached_data
|
|
199
|
+
|
|
200
|
+
# Fetch fresh data
|
|
201
|
+
return self._fetch_fresh_ssm_data()
|
|
202
|
+
|
|
203
|
+
def _fetch_fresh_ssm_data(self) -> Dict[str, Any]:
|
|
204
|
+
"""
|
|
205
|
+
Fetch fresh SSM data from AWS API.
|
|
206
|
+
|
|
207
|
+
:return: SSM configuration data
|
|
208
|
+
:rtype: Dict[str, Any]
|
|
209
|
+
"""
|
|
210
|
+
logger.info(f"Fetching Systems Manager configurations from AWS region: {self.region}")
|
|
211
|
+
|
|
212
|
+
# Initialize AWS session
|
|
213
|
+
if not self.session:
|
|
214
|
+
self._initialize_aws_session()
|
|
215
|
+
|
|
216
|
+
# Create SSM collector
|
|
217
|
+
self.collector = SystemsManagerCollector(
|
|
218
|
+
session=self.session, region=self.region, account_id=self.account_id, tags=self.tags
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
# Collect SSM data
|
|
222
|
+
self.raw_ssm_data = self.collector.collect()
|
|
223
|
+
|
|
224
|
+
managed_instances = len(self.raw_ssm_data.get("ManagedInstances", []))
|
|
225
|
+
parameters = len(self.raw_ssm_data.get("Parameters", []))
|
|
226
|
+
documents = len(self.raw_ssm_data.get("Documents", []))
|
|
227
|
+
patch_baselines = len(self.raw_ssm_data.get("PatchBaselines", []))
|
|
228
|
+
|
|
229
|
+
logger.info(
|
|
230
|
+
f"Collected SSM data: {managed_instances} instances, {parameters} parameters, "
|
|
231
|
+
f"{documents} documents, {patch_baselines} patch baselines"
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
# Save to cache
|
|
235
|
+
self._save_cache(self.raw_ssm_data)
|
|
236
|
+
|
|
237
|
+
return self.raw_ssm_data
|
|
238
|
+
|
|
239
|
+
def sync_compliance_data(self) -> None:
|
|
240
|
+
"""Sync Systems Manager compliance data to RegScale."""
|
|
241
|
+
logger.info("Starting AWS Systems Manager compliance data sync to RegScale")
|
|
242
|
+
|
|
243
|
+
# Fetch SSM data
|
|
244
|
+
ssm_data = self.fetch_compliance_data()
|
|
245
|
+
if not ssm_data:
|
|
246
|
+
logger.warning("No Systems Manager data to sync")
|
|
247
|
+
return
|
|
248
|
+
|
|
249
|
+
# Convert to compliance item
|
|
250
|
+
self.ssm_item = SSMComplianceItem(ssm_data)
|
|
251
|
+
logger.info("Processing Systems Manager configuration for compliance assessment")
|
|
252
|
+
|
|
253
|
+
# Assess compliance
|
|
254
|
+
compliance_results = self._assess_compliance()
|
|
255
|
+
|
|
256
|
+
# Populate control dictionaries for assessment creation
|
|
257
|
+
if self.update_control_status:
|
|
258
|
+
self._populate_control_results(compliance_results["overall"])
|
|
259
|
+
# Create control assessments and update implementation statuses
|
|
260
|
+
self._process_control_assessments()
|
|
261
|
+
|
|
262
|
+
# Create evidence artifacts
|
|
263
|
+
if self.create_evidence or self.create_ssp_attachment:
|
|
264
|
+
self._create_evidence_artifacts(compliance_results)
|
|
265
|
+
|
|
266
|
+
logger.info("AWS Systems Manager compliance sync completed successfully")
|
|
267
|
+
|
|
268
|
+
def create_compliance_item(self, raw_data: Dict[str, Any]):
|
|
269
|
+
"""
|
|
270
|
+
Create a ComplianceItem from raw SSM data.
|
|
271
|
+
|
|
272
|
+
:param Dict[str, Any] raw_data: Raw SSM configuration data
|
|
273
|
+
:return: SSMComplianceItem instance
|
|
274
|
+
:rtype: SSMComplianceItem
|
|
275
|
+
"""
|
|
276
|
+
return SSMComplianceItem(raw_data)
|
|
277
|
+
|
|
278
|
+
def _assess_compliance(self) -> Dict[str, Any]:
|
|
279
|
+
"""
|
|
280
|
+
Assess Systems Manager compliance against NIST controls.
|
|
281
|
+
|
|
282
|
+
:return: Compliance assessment results
|
|
283
|
+
:rtype: Dict[str, Any]
|
|
284
|
+
"""
|
|
285
|
+
logger.info("Assessing Systems Manager compliance against NIST 800-53 R5 controls")
|
|
286
|
+
|
|
287
|
+
# Assess overall compliance
|
|
288
|
+
overall_results = self.control_mapper.assess_ssm_compliance(self.ssm_item.to_dict())
|
|
289
|
+
|
|
290
|
+
# Log summary
|
|
291
|
+
passed_controls = [ctrl for ctrl, result in overall_results.items() if result == "PASS"]
|
|
292
|
+
failed_controls = [ctrl for ctrl, result in overall_results.items() if result == "FAIL"]
|
|
293
|
+
|
|
294
|
+
logger.info("Systems Manager Compliance Assessment Summary:")
|
|
295
|
+
logger.info(f" Managed Instances: {len(self.ssm_item.managed_instances)}")
|
|
296
|
+
logger.info(f" Parameters: {len(self.ssm_item.parameters)}")
|
|
297
|
+
logger.info(f" Documents: {len(self.ssm_item.documents)}")
|
|
298
|
+
logger.info(f" Patch Baselines: {len(self.ssm_item.patch_baselines)}")
|
|
299
|
+
logger.info(f" Controls Passed: {len(passed_controls)} - {', '.join(passed_controls)}")
|
|
300
|
+
logger.info(f" Controls Failed: {len(failed_controls)} - {', '.join(failed_controls)}")
|
|
301
|
+
|
|
302
|
+
return {"overall": overall_results}
|
|
303
|
+
|
|
304
|
+
def _populate_control_results(self, control_results: Dict[str, str]) -> None:
|
|
305
|
+
"""
|
|
306
|
+
Populate passing_controls and failing_controls dictionaries from assessment results.
|
|
307
|
+
|
|
308
|
+
This method converts the control-level assessment results into the format expected
|
|
309
|
+
by the base class _process_control_assessments() method.
|
|
310
|
+
|
|
311
|
+
:param Dict[str, str] control_results: Control assessment results (e.g., {"AC-2": "PASS", "AC-3": "FAIL"})
|
|
312
|
+
:return: None
|
|
313
|
+
:rtype: None
|
|
314
|
+
"""
|
|
315
|
+
for control_id, result in control_results.items():
|
|
316
|
+
# Normalize control ID to lowercase for consistent lookup
|
|
317
|
+
control_key = control_id.lower()
|
|
318
|
+
|
|
319
|
+
# Create a simple compliance item placeholder for the base class
|
|
320
|
+
if result in self.PASS_STATUSES:
|
|
321
|
+
self.passing_controls[control_key] = self.ssm_item
|
|
322
|
+
elif result in self.FAIL_STATUSES:
|
|
323
|
+
self.failing_controls[control_key] = self.ssm_item
|
|
324
|
+
|
|
325
|
+
logger.debug(
|
|
326
|
+
f"Populated control results: {len(self.passing_controls)} passing, {len(self.failing_controls)} failing"
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
def _create_evidence_artifacts(self, compliance_results: Dict[str, Any]) -> None:
|
|
330
|
+
"""
|
|
331
|
+
Create evidence artifacts in RegScale.
|
|
332
|
+
|
|
333
|
+
:param Dict compliance_results: Compliance assessment results
|
|
334
|
+
"""
|
|
335
|
+
logger.info("Creating Systems Manager evidence artifacts in RegScale")
|
|
336
|
+
|
|
337
|
+
# Create comprehensive evidence file
|
|
338
|
+
evidence_file_path = self._create_evidence_file(compliance_results)
|
|
339
|
+
|
|
340
|
+
if self.create_ssp_attachment:
|
|
341
|
+
self._create_ssp_attachment_with_evidence(evidence_file_path)
|
|
342
|
+
|
|
343
|
+
# Clean up temporary file
|
|
344
|
+
if os.path.exists(evidence_file_path):
|
|
345
|
+
os.remove(evidence_file_path)
|
|
346
|
+
logger.debug(f"Cleaned up temporary evidence file: {evidence_file_path}")
|
|
347
|
+
|
|
348
|
+
def _create_evidence_file(self, compliance_results: Dict[str, Any]) -> str:
|
|
349
|
+
"""
|
|
350
|
+
Create JSONL.GZ evidence file with Systems Manager configuration data.
|
|
351
|
+
|
|
352
|
+
:param Dict compliance_results: Compliance assessment results
|
|
353
|
+
:return: Path to created evidence file
|
|
354
|
+
:rtype: str
|
|
355
|
+
"""
|
|
356
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
357
|
+
evidence_file = os.path.join(tempfile.gettempdir(), f"ssm_evidence_{self.region}_{timestamp}.jsonl.gz")
|
|
358
|
+
|
|
359
|
+
try:
|
|
360
|
+
with gzip.open(evidence_file, "wt", encoding="utf-8") as f:
|
|
361
|
+
# Write metadata
|
|
362
|
+
metadata = {
|
|
363
|
+
"type": "metadata",
|
|
364
|
+
"timestamp": datetime.now().isoformat(),
|
|
365
|
+
"region": self.region,
|
|
366
|
+
"account_id": self.account_id,
|
|
367
|
+
"managed_instances_count": len(self.ssm_item.managed_instances),
|
|
368
|
+
"parameters_count": len(self.ssm_item.parameters),
|
|
369
|
+
"documents_count": len(self.ssm_item.documents),
|
|
370
|
+
"patch_baselines_count": len(self.ssm_item.patch_baselines),
|
|
371
|
+
"compliance_framework": "NIST800-53R5",
|
|
372
|
+
}
|
|
373
|
+
f.write(json.dumps(metadata) + "\n")
|
|
374
|
+
|
|
375
|
+
# Write compliance summary
|
|
376
|
+
summary = {"type": "compliance_summary", "results": compliance_results["overall"]}
|
|
377
|
+
f.write(json.dumps(summary) + "\n")
|
|
378
|
+
|
|
379
|
+
# Write managed instances
|
|
380
|
+
for instance in self.ssm_item.managed_instances:
|
|
381
|
+
instance_record = {
|
|
382
|
+
"type": "managed_instance",
|
|
383
|
+
"instance_id": instance.get("InstanceId"),
|
|
384
|
+
"ping_status": instance.get("PingStatus"),
|
|
385
|
+
"platform": instance.get("PlatformName"),
|
|
386
|
+
"agent_version": instance.get("AgentVersion"),
|
|
387
|
+
"patch_summary": instance.get("PatchSummary", {}),
|
|
388
|
+
}
|
|
389
|
+
f.write(json.dumps(instance_record, default=str) + "\n")
|
|
390
|
+
|
|
391
|
+
# Write patch baselines
|
|
392
|
+
for baseline in self.ssm_item.patch_baselines:
|
|
393
|
+
baseline_record = {
|
|
394
|
+
"type": "patch_baseline",
|
|
395
|
+
"baseline_id": baseline.get("BaselineId"),
|
|
396
|
+
"name": baseline.get("BaselineName"),
|
|
397
|
+
"os": baseline.get("OperatingSystem"),
|
|
398
|
+
"default": baseline.get("DefaultBaseline", False),
|
|
399
|
+
}
|
|
400
|
+
f.write(json.dumps(baseline_record, default=str) + "\n")
|
|
401
|
+
|
|
402
|
+
# Write maintenance windows
|
|
403
|
+
for window in self.ssm_item.maintenance_windows:
|
|
404
|
+
window_record = {
|
|
405
|
+
"type": "maintenance_window",
|
|
406
|
+
"window_id": window.get("WindowId"),
|
|
407
|
+
"name": window.get("Name"),
|
|
408
|
+
"enabled": window.get("Enabled", False),
|
|
409
|
+
"schedule": window.get("Schedule"),
|
|
410
|
+
}
|
|
411
|
+
f.write(json.dumps(window_record, default=str) + "\n")
|
|
412
|
+
|
|
413
|
+
# Write compliance summary
|
|
414
|
+
if self.ssm_item.compliance_summary:
|
|
415
|
+
compliance_record = {
|
|
416
|
+
"type": "compliance_data",
|
|
417
|
+
"total_compliant": self.ssm_item.compliance_summary.get("TotalCompliant", 0),
|
|
418
|
+
"total_non_compliant": self.ssm_item.compliance_summary.get("TotalNonCompliant", 0),
|
|
419
|
+
"compliance_types": self.ssm_item.compliance_summary.get("ComplianceTypes", []),
|
|
420
|
+
}
|
|
421
|
+
f.write(json.dumps(compliance_record, default=str) + "\n")
|
|
422
|
+
|
|
423
|
+
logger.info(f"Created evidence file: {evidence_file}")
|
|
424
|
+
return evidence_file
|
|
425
|
+
|
|
426
|
+
except Exception as e:
|
|
427
|
+
logger.error(f"Failed to create evidence file: {e}", exc_info=True)
|
|
428
|
+
raise
|
|
429
|
+
|
|
430
|
+
def _create_ssp_attachment_with_evidence(self, evidence_file_path: str) -> None:
|
|
431
|
+
"""
|
|
432
|
+
Create SSP attachment with Systems Manager evidence.
|
|
433
|
+
|
|
434
|
+
:param str evidence_file_path: Path to evidence file
|
|
435
|
+
"""
|
|
436
|
+
try:
|
|
437
|
+
date_str = datetime.now().strftime("%Y%m%d")
|
|
438
|
+
file_name_pattern = f"ssm_evidence_{self.region}_{date_str}"
|
|
439
|
+
|
|
440
|
+
# Check if evidence for today already exists using base class method
|
|
441
|
+
if self.check_for_existing_evidence(file_name_pattern):
|
|
442
|
+
logger.info(
|
|
443
|
+
f"Evidence file for Systems Manager in region {self.region} already exists for today. "
|
|
444
|
+
"Skipping upload to avoid duplicates."
|
|
445
|
+
)
|
|
446
|
+
return
|
|
447
|
+
|
|
448
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
449
|
+
file_name = f"ssm_evidence_{self.region}_{timestamp}.jsonl.gz"
|
|
450
|
+
|
|
451
|
+
# Read the compressed file
|
|
452
|
+
with open(evidence_file_path, "rb") as f:
|
|
453
|
+
file_data = f.read()
|
|
454
|
+
|
|
455
|
+
# Upload file to RegScale
|
|
456
|
+
success = File.upload_file_to_regscale(
|
|
457
|
+
file_name=file_name,
|
|
458
|
+
parent_id=self.plan_id,
|
|
459
|
+
parent_module="securityplans",
|
|
460
|
+
api=self.api,
|
|
461
|
+
file_data=file_data,
|
|
462
|
+
tags="aws,ssm,systems-manager,patch,config,compliance,automated",
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
if success:
|
|
466
|
+
logger.info(f"Successfully uploaded Systems Manager evidence file: {file_name}")
|
|
467
|
+
# Note: SSP attachments don't return IDs from upload_file_to_regscale
|
|
468
|
+
# Control linking would need to be implemented if required for attachments
|
|
469
|
+
if self.evidence_control_ids:
|
|
470
|
+
pass # Placeholder for future SSP attachment-to-control linking
|
|
471
|
+
else:
|
|
472
|
+
logger.error("Failed to upload Systems Manager evidence file")
|
|
473
|
+
|
|
474
|
+
except Exception as e:
|
|
475
|
+
logger.error(f"Failed to create SSP attachment: {e}", exc_info=True)
|
|
476
|
+
|
|
477
|
+
def _link_evidence_to_controls(self, evidence_id: int, is_attachment: bool = False) -> None:
|
|
478
|
+
"""
|
|
479
|
+
Link evidence to specified control IDs.
|
|
480
|
+
|
|
481
|
+
:param int evidence_id: Evidence or attachment ID
|
|
482
|
+
:param bool is_attachment: True if linking attachment, False for evidence record
|
|
483
|
+
"""
|
|
484
|
+
try:
|
|
485
|
+
for control_id in self.evidence_control_ids:
|
|
486
|
+
if is_attachment:
|
|
487
|
+
self.api.link_ssp_attachment_to_control(self.plan_id, evidence_id, control_id)
|
|
488
|
+
else:
|
|
489
|
+
self.api.link_evidence_to_control(evidence_id, control_id)
|
|
490
|
+
logger.info(f"Linked evidence {evidence_id} to control {control_id}")
|
|
491
|
+
except Exception as e:
|
|
492
|
+
logger.error(f"Failed to link evidence to controls: {e}", exc_info=True)
|