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
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
import json
|
|
4
4
|
import logging
|
|
5
5
|
import os
|
|
6
|
+
from dataclasses import dataclass
|
|
6
7
|
from datetime import datetime
|
|
7
|
-
from typing import Optional
|
|
8
|
+
from typing import Dict, Optional, Tuple, Any, List
|
|
8
9
|
|
|
9
10
|
import click
|
|
10
11
|
|
|
@@ -13,6 +14,229 @@ from regscale.models.integration_models.flat_file_importer import FlatFileImport
|
|
|
13
14
|
logger = logging.getLogger("regscale")
|
|
14
15
|
|
|
15
16
|
|
|
17
|
+
@dataclass
|
|
18
|
+
class ComplianceSyncConfig:
|
|
19
|
+
"""Configuration for AWS Audit Manager compliance sync."""
|
|
20
|
+
|
|
21
|
+
region: str
|
|
22
|
+
regscale_id: int
|
|
23
|
+
framework: str = "NIST800-53R5"
|
|
24
|
+
custom_framework_name: Optional[str] = None
|
|
25
|
+
assessment_id: Optional[str] = None
|
|
26
|
+
create_issues: bool = True
|
|
27
|
+
update_control_status: bool = True
|
|
28
|
+
create_poams: bool = False
|
|
29
|
+
collect_evidence: bool = False
|
|
30
|
+
evidence_control_ids: Optional[List[str]] = None
|
|
31
|
+
evidence_frequency: int = 30
|
|
32
|
+
max_evidence_per_control: int = 100
|
|
33
|
+
use_assessment_evidence_folders: bool = True
|
|
34
|
+
force_refresh: bool = False
|
|
35
|
+
use_enhanced_analyzer: bool = False
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Evidence collection constants
|
|
39
|
+
EVIDENCE_MODE_SSP_ATTACHMENTS = "Evidence collection mode: SSP file attachments (default)"
|
|
40
|
+
EVIDENCE_MODE_INDIVIDUAL_RECORDS = "Evidence collection mode: Individual Evidence records"
|
|
41
|
+
DEFAULT_EVIDENCE_FREQUENCY_DAYS = 30 # Default evidence update frequency in days
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class AWSCredentialConfig:
|
|
46
|
+
"""AWS credential configuration."""
|
|
47
|
+
|
|
48
|
+
session_name: Optional[str] = None
|
|
49
|
+
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
|
+
account_id: Optional[str] = None
|
|
54
|
+
tags: Optional[Dict[str, str]] = None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class FindingsSyncConfig:
|
|
59
|
+
"""Configuration for AWS findings sync."""
|
|
60
|
+
|
|
61
|
+
region: str
|
|
62
|
+
regscale_id: int
|
|
63
|
+
credentials: AWSCredentialConfig
|
|
64
|
+
generate_evidence: bool = False
|
|
65
|
+
control_ids: Optional[str] = None
|
|
66
|
+
format: str = "native"
|
|
67
|
+
import_all_findings: bool = False
|
|
68
|
+
evidence_frequency: int = DEFAULT_EVIDENCE_FREQUENCY_DAYS
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass
|
|
72
|
+
class ConfigComplianceConfig:
|
|
73
|
+
"""Configuration for AWS Config compliance sync."""
|
|
74
|
+
|
|
75
|
+
region: str
|
|
76
|
+
regscale_id: int
|
|
77
|
+
framework: str = "NIST800-53R5"
|
|
78
|
+
conformance_pack_name: Optional[str] = None
|
|
79
|
+
create_issues: bool = True
|
|
80
|
+
update_control_status: bool = True
|
|
81
|
+
create_poams: bool = False
|
|
82
|
+
collect_evidence: bool = False
|
|
83
|
+
evidence_as_attachments: bool = True
|
|
84
|
+
evidence_as_records: bool = False
|
|
85
|
+
evidence_control_ids: Optional[List[str]] = None
|
|
86
|
+
evidence_frequency: int = 30
|
|
87
|
+
use_security_hub: bool = False
|
|
88
|
+
force_refresh: bool = False
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@dataclass
|
|
92
|
+
class KMSConfig:
|
|
93
|
+
"""Configuration for AWS KMS evidence collection."""
|
|
94
|
+
|
|
95
|
+
region: str
|
|
96
|
+
regscale_ssp_id: int
|
|
97
|
+
collect_kms_evidence: bool = True
|
|
98
|
+
kms_evidence_control_ids: Optional[List[str]] = None
|
|
99
|
+
kms_evidence_frequency: int = 30
|
|
100
|
+
kms_evidence_mode: str = "attachments"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@dataclass
|
|
104
|
+
class OrganizationConfig:
|
|
105
|
+
"""Configuration for AWS Organizations evidence collection."""
|
|
106
|
+
|
|
107
|
+
region: str
|
|
108
|
+
regscale_ssp_id: int
|
|
109
|
+
collect_org_evidence: bool = True
|
|
110
|
+
org_evidence_control_ids: Optional[List[str]] = None
|
|
111
|
+
org_evidence_frequency: int = 30
|
|
112
|
+
org_evidence_mode: str = "attachments"
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@dataclass
|
|
116
|
+
class IAMConfig:
|
|
117
|
+
"""Configuration for AWS IAM evidence collection."""
|
|
118
|
+
|
|
119
|
+
region: str
|
|
120
|
+
regscale_ssp_id: int
|
|
121
|
+
collect_iam_evidence: bool = True
|
|
122
|
+
iam_evidence_control_ids: Optional[List[str]] = None
|
|
123
|
+
iam_evidence_frequency: int = 30
|
|
124
|
+
iam_evidence_mode: str = "attachments"
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@dataclass
|
|
128
|
+
class GuardDutyConfig:
|
|
129
|
+
"""Configuration for AWS GuardDuty evidence collection."""
|
|
130
|
+
|
|
131
|
+
region: str
|
|
132
|
+
regscale_ssp_id: int
|
|
133
|
+
collect_guardduty_evidence: bool = True
|
|
134
|
+
guardduty_evidence_control_ids: Optional[List[str]] = None
|
|
135
|
+
guardduty_evidence_frequency: int = 30
|
|
136
|
+
guardduty_evidence_mode: str = "attachments"
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@dataclass
|
|
140
|
+
class S3Config:
|
|
141
|
+
"""Configuration for AWS S3 evidence collection."""
|
|
142
|
+
|
|
143
|
+
region: str
|
|
144
|
+
regscale_ssp_id: int
|
|
145
|
+
collect_s3_evidence: bool = True
|
|
146
|
+
s3_evidence_control_ids: Optional[List[str]] = None
|
|
147
|
+
s3_evidence_frequency: int = 30
|
|
148
|
+
s3_evidence_mode: str = "attachments"
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@dataclass
|
|
152
|
+
class CloudTrailConfig:
|
|
153
|
+
"""Configuration for AWS CloudTrail evidence collection."""
|
|
154
|
+
|
|
155
|
+
region: str
|
|
156
|
+
regscale_ssp_id: int
|
|
157
|
+
collect_cloudtrail_evidence: bool = True
|
|
158
|
+
cloudtrail_evidence_control_ids: Optional[List[str]] = None
|
|
159
|
+
cloudtrail_evidence_frequency: int = 30
|
|
160
|
+
cloudtrail_evidence_mode: str = "attachments"
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def parse_tags(tags_string: Optional[str]) -> Optional[Dict[str, str]]:
|
|
164
|
+
"""
|
|
165
|
+
Parse tag string into dictionary.
|
|
166
|
+
|
|
167
|
+
Format: key1=value1,key2=value2
|
|
168
|
+
|
|
169
|
+
:param Optional[str] tags_string: Comma-separated key=value pairs
|
|
170
|
+
:return: Dictionary of tag key-value pairs or None if input is empty
|
|
171
|
+
:rtype: Optional[Dict[str, str]]
|
|
172
|
+
"""
|
|
173
|
+
if not tags_string:
|
|
174
|
+
return None
|
|
175
|
+
|
|
176
|
+
tag_dict = {}
|
|
177
|
+
try:
|
|
178
|
+
for tag_pair in tags_string.split(","):
|
|
179
|
+
if "=" not in tag_pair:
|
|
180
|
+
logger.warning(f"Invalid tag format (missing '='): {tag_pair}")
|
|
181
|
+
continue
|
|
182
|
+
key, value = tag_pair.split("=", 1)
|
|
183
|
+
tag_dict[key.strip()] = value.strip()
|
|
184
|
+
return tag_dict if tag_dict else None
|
|
185
|
+
except (ValueError, AttributeError) as e:
|
|
186
|
+
logger.error(f"Error parsing tags: {e}")
|
|
187
|
+
raise click.ClickException("Invalid tag format. Expected format: key1=value1,key2=value2")
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def resolve_aws_credentials(
|
|
191
|
+
session_name: Optional[str],
|
|
192
|
+
profile: Optional[str],
|
|
193
|
+
aws_access_key_id: Optional[str],
|
|
194
|
+
aws_secret_access_key: Optional[str],
|
|
195
|
+
aws_session_token: Optional[str],
|
|
196
|
+
region: Optional[str] = None,
|
|
197
|
+
) -> Tuple[Optional[str], Optional[str], Optional[str], Optional[str], Optional[str]]:
|
|
198
|
+
"""
|
|
199
|
+
Resolve AWS credentials from session cache, profile, or explicit credentials.
|
|
200
|
+
|
|
201
|
+
Priority order:
|
|
202
|
+
1. Cached session (if session_name provided)
|
|
203
|
+
2. Explicit credentials (if provided)
|
|
204
|
+
3. Profile (if provided)
|
|
205
|
+
4. Environment variables / default credential chain
|
|
206
|
+
|
|
207
|
+
:param Optional[str] session_name: Name of cached session to use
|
|
208
|
+
:param Optional[str] profile: AWS profile name
|
|
209
|
+
:param Optional[str] aws_access_key_id: Explicit access key ID
|
|
210
|
+
:param Optional[str] aws_secret_access_key: Explicit secret access key
|
|
211
|
+
:param Optional[str] aws_session_token: Explicit session token
|
|
212
|
+
:param Optional[str] region: AWS region
|
|
213
|
+
:return: Tuple of (profile, access_key_id, secret_access_key, session_token, region)
|
|
214
|
+
:rtype: Tuple[Optional[str], Optional[str], Optional[str], Optional[str], Optional[str]]
|
|
215
|
+
"""
|
|
216
|
+
# If session-name is provided, try to get credentials from cache
|
|
217
|
+
if session_name:
|
|
218
|
+
from .session_manager import AWSSessionManager
|
|
219
|
+
|
|
220
|
+
manager = AWSSessionManager()
|
|
221
|
+
cached_creds = manager.get_credentials_for_session(session_name)
|
|
222
|
+
|
|
223
|
+
if cached_creds:
|
|
224
|
+
cached_access_key, cached_secret_key, cached_session_token, cached_region = cached_creds
|
|
225
|
+
logger.info(f"Using cached AWS session: {session_name}")
|
|
226
|
+
return (
|
|
227
|
+
None, # Don't use profile when using cached session
|
|
228
|
+
cached_access_key,
|
|
229
|
+
cached_secret_key,
|
|
230
|
+
cached_session_token,
|
|
231
|
+
region or cached_region,
|
|
232
|
+
)
|
|
233
|
+
else:
|
|
234
|
+
logger.warning(f"Cached session '{session_name}' not found or expired. Falling back to other methods.")
|
|
235
|
+
|
|
236
|
+
# Otherwise, use provided credentials or profile
|
|
237
|
+
return (profile, aws_access_key_id, aws_secret_access_key, aws_session_token, region)
|
|
238
|
+
|
|
239
|
+
|
|
16
240
|
@click.group(name="aws")
|
|
17
241
|
def awsv2():
|
|
18
242
|
"""AWS Integrations."""
|
|
@@ -33,33 +257,70 @@ def awsv2():
|
|
|
33
257
|
help="RegScale will create and update assets as children of this record.",
|
|
34
258
|
required=True,
|
|
35
259
|
)
|
|
260
|
+
@click.option(
|
|
261
|
+
"--session-name",
|
|
262
|
+
type=str,
|
|
263
|
+
required=False,
|
|
264
|
+
help="Name of cached AWS session to use (from 'regscale aws auth login')",
|
|
265
|
+
)
|
|
266
|
+
@click.option(
|
|
267
|
+
"--profile",
|
|
268
|
+
type=str,
|
|
269
|
+
required=False,
|
|
270
|
+
help="AWS profile name from ~/.aws/credentials",
|
|
271
|
+
envvar="AWS_PROFILE",
|
|
272
|
+
)
|
|
36
273
|
@click.option(
|
|
37
274
|
"--aws_access_key_id",
|
|
38
275
|
type=str,
|
|
39
276
|
required=False,
|
|
40
|
-
help="AWS access key ID",
|
|
277
|
+
help="AWS access key ID (overrides profile)",
|
|
41
278
|
envvar="AWS_ACCESS_KEY_ID",
|
|
42
279
|
)
|
|
43
280
|
@click.option(
|
|
44
281
|
"--aws_secret_access_key",
|
|
45
282
|
type=str,
|
|
46
283
|
required=False,
|
|
47
|
-
help="AWS secret access key",
|
|
284
|
+
help="AWS secret access key (overrides profile)",
|
|
48
285
|
default=os.getenv("AWS_SECRET_ACCESS_KEY"),
|
|
49
286
|
)
|
|
50
287
|
@click.option(
|
|
51
288
|
"--aws_session_token",
|
|
52
289
|
type=click.STRING,
|
|
53
290
|
required=False,
|
|
54
|
-
help="AWS
|
|
291
|
+
help="AWS session token (overrides profile)",
|
|
55
292
|
default=os.environ.get("AWS_SESSION_TOKEN"),
|
|
56
293
|
)
|
|
294
|
+
@click.option(
|
|
295
|
+
"--account-id",
|
|
296
|
+
type=str,
|
|
297
|
+
required=False,
|
|
298
|
+
help="Filter resources by AWS account ID",
|
|
299
|
+
envvar="AWS_ACCOUNT_ID",
|
|
300
|
+
)
|
|
301
|
+
@click.option(
|
|
302
|
+
"--tags",
|
|
303
|
+
type=str,
|
|
304
|
+
required=False,
|
|
305
|
+
help="Filter resources by tags (format: key1=value1,key2=value2). All tags must match.",
|
|
306
|
+
)
|
|
307
|
+
@click.option(
|
|
308
|
+
"--force-refresh",
|
|
309
|
+
is_flag=True,
|
|
310
|
+
default=False,
|
|
311
|
+
help="Force refresh AWS inventory data, ignoring cached data even if it's still valid.",
|
|
312
|
+
)
|
|
57
313
|
def sync_assets(
|
|
58
314
|
region: str,
|
|
59
315
|
regscale_id: int,
|
|
316
|
+
session_name: Optional[str] = None,
|
|
317
|
+
profile: Optional[str] = None,
|
|
60
318
|
aws_access_key_id: Optional[str] = None,
|
|
61
319
|
aws_secret_access_key: Optional[str] = None,
|
|
62
320
|
aws_session_token: Optional[str] = None,
|
|
321
|
+
account_id: Optional[str] = None,
|
|
322
|
+
tags: Optional[str] = None,
|
|
323
|
+
force_refresh: bool = False,
|
|
63
324
|
) -> None:
|
|
64
325
|
"""
|
|
65
326
|
Sync AWS resources to RegScale assets.
|
|
@@ -73,21 +334,60 @@ def sync_assets(
|
|
|
73
334
|
- VPCs and networking resources
|
|
74
335
|
- Container resources
|
|
75
336
|
- And more...
|
|
337
|
+
|
|
338
|
+
Caching Behavior:
|
|
339
|
+
AWS inventory data is cached for 8 hours in artifacts/aws/inventory.json to improve performance.
|
|
340
|
+
The cache is shared across all SSPs for the same AWS account/region.
|
|
341
|
+
Use --force-refresh to bypass the cache and fetch fresh data from AWS.
|
|
342
|
+
|
|
343
|
+
Note: "Updated" assets in the output are resources that already exist in the target SSP
|
|
344
|
+
(from a previous sync). This is normal behavior when syncing to an SSP multiple times.
|
|
345
|
+
|
|
346
|
+
Filtering Options:
|
|
347
|
+
Use --account-id to filter resources by AWS account ID.
|
|
348
|
+
Use --tags to filter resources by tags (format: Environment=prod,Team=security).
|
|
349
|
+
Both filters use AND logic - all criteria must match.
|
|
350
|
+
|
|
351
|
+
Authentication methods (in priority order):
|
|
352
|
+
1. Cached session: --session-name (from 'regscale aws auth login')
|
|
353
|
+
2. Explicit credentials: --aws_access_key_id + --aws_secret_access_key
|
|
354
|
+
3. AWS profile: --profile
|
|
355
|
+
4. Environment variables or default AWS credential chain
|
|
76
356
|
"""
|
|
77
357
|
try:
|
|
78
358
|
logger.info("Starting AWS asset sync to RegScale...")
|
|
79
359
|
from .scanner import AWSInventoryIntegration
|
|
80
360
|
|
|
81
|
-
|
|
82
|
-
|
|
361
|
+
# Parse tags
|
|
362
|
+
tag_dict = parse_tags(tags)
|
|
363
|
+
if tag_dict:
|
|
364
|
+
logger.info(f"Filtering resources by tags: {tag_dict}")
|
|
365
|
+
if account_id:
|
|
366
|
+
logger.info(f"Filtering resources by account ID: {account_id}")
|
|
367
|
+
|
|
368
|
+
# Resolve credentials from session cache or other methods
|
|
369
|
+
profile, aws_access_key_id, aws_secret_access_key, aws_session_token, region = resolve_aws_credentials(
|
|
370
|
+
session_name, profile, aws_access_key_id, aws_secret_access_key, aws_session_token, region
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
if force_refresh:
|
|
374
|
+
logger.info("Force refresh enabled - clearing cached inventory data")
|
|
375
|
+
|
|
376
|
+
AWSInventoryIntegration.sync_assets(
|
|
83
377
|
plan_id=regscale_id,
|
|
84
378
|
region=region,
|
|
379
|
+
profile=profile,
|
|
85
380
|
aws_access_key_id=aws_access_key_id,
|
|
86
381
|
aws_secret_access_key=aws_secret_access_key,
|
|
87
382
|
aws_session_token=aws_session_token,
|
|
383
|
+
account_id=account_id,
|
|
384
|
+
tags=tag_dict,
|
|
385
|
+
force_refresh=force_refresh,
|
|
88
386
|
)
|
|
89
387
|
logger.info("AWS asset sync completed successfully.")
|
|
90
|
-
except
|
|
388
|
+
except (
|
|
389
|
+
Exception
|
|
390
|
+
) as e: # Broad catch appropriate for CLI command - may raise AWS SDK, network, or RegScale API errors
|
|
91
391
|
logger.error(f"Error syncing AWS assets: {e}", exc_info=True)
|
|
92
392
|
raise click.ClickException(str(e))
|
|
93
393
|
|
|
@@ -105,27 +405,53 @@ def inventory():
|
|
|
105
405
|
default=os.getenv("AWS_REGION", "us-east-1"),
|
|
106
406
|
help="AWS region to collect inventory from. Default is us-east-1.",
|
|
107
407
|
)
|
|
408
|
+
@click.option(
|
|
409
|
+
"--session-name",
|
|
410
|
+
type=str,
|
|
411
|
+
required=False,
|
|
412
|
+
help="Name of cached AWS session to use (from 'regscale aws auth login')",
|
|
413
|
+
)
|
|
414
|
+
@click.option(
|
|
415
|
+
"--profile",
|
|
416
|
+
type=str,
|
|
417
|
+
required=False,
|
|
418
|
+
help="AWS profile name from ~/.aws/credentials",
|
|
419
|
+
envvar="AWS_PROFILE",
|
|
420
|
+
)
|
|
108
421
|
@click.option(
|
|
109
422
|
"--aws_access_key_id",
|
|
110
423
|
type=str,
|
|
111
424
|
required=False,
|
|
112
|
-
help="AWS access key ID",
|
|
425
|
+
help="AWS access key ID (overrides profile)",
|
|
113
426
|
envvar="AWS_ACCESS_KEY_ID",
|
|
114
427
|
)
|
|
115
428
|
@click.option(
|
|
116
429
|
"--aws_secret_access_key",
|
|
117
430
|
type=str,
|
|
118
431
|
required=False,
|
|
119
|
-
help="AWS secret access key",
|
|
432
|
+
help="AWS secret access key (overrides profile)",
|
|
120
433
|
envvar="AWS_SECRET_ACCESS_KEY",
|
|
121
434
|
)
|
|
122
435
|
@click.option(
|
|
123
436
|
"--aws_session_token",
|
|
124
437
|
type=click.STRING,
|
|
125
438
|
required=False,
|
|
126
|
-
help="AWS
|
|
439
|
+
help="AWS session token (overrides profile)",
|
|
127
440
|
default=os.environ.get("AWS_SESSION_TOKEN"),
|
|
128
441
|
)
|
|
442
|
+
@click.option(
|
|
443
|
+
"--account-id",
|
|
444
|
+
type=str,
|
|
445
|
+
required=False,
|
|
446
|
+
help="Filter resources by AWS account ID",
|
|
447
|
+
envvar="AWS_ACCOUNT_ID",
|
|
448
|
+
)
|
|
449
|
+
@click.option(
|
|
450
|
+
"--tags",
|
|
451
|
+
type=str,
|
|
452
|
+
required=False,
|
|
453
|
+
help="Filter resources by tags (format: key1=value1,key2=value2). All tags must match.",
|
|
454
|
+
)
|
|
129
455
|
@click.option(
|
|
130
456
|
"--output",
|
|
131
457
|
type=click.Path(dir_okay=False, writable=True),
|
|
@@ -134,9 +460,13 @@ def inventory():
|
|
|
134
460
|
)
|
|
135
461
|
def collect_inventory(
|
|
136
462
|
region: str,
|
|
463
|
+
session_name: Optional[str],
|
|
464
|
+
profile: Optional[str],
|
|
137
465
|
aws_access_key_id: Optional[str],
|
|
138
466
|
aws_secret_access_key: Optional[str],
|
|
139
467
|
aws_session_token: Optional[str],
|
|
468
|
+
account_id: Optional[str],
|
|
469
|
+
tags: Optional[str],
|
|
140
470
|
output: Optional[str],
|
|
141
471
|
) -> None:
|
|
142
472
|
"""
|
|
@@ -150,17 +480,44 @@ def collect_inventory(
|
|
|
150
480
|
- And more...
|
|
151
481
|
|
|
152
482
|
The inventory can be displayed to stdout or saved to a JSON file.
|
|
483
|
+
|
|
484
|
+
Filtering Options:
|
|
485
|
+
Use --account-id to filter resources by AWS account ID.
|
|
486
|
+
Use --tags to filter resources by tags (format: Environment=prod,Team=security).
|
|
487
|
+
Both filters use AND logic - all criteria must match.
|
|
488
|
+
|
|
489
|
+
Authentication methods (in priority order):
|
|
490
|
+
1. Cached session: --session-name (from 'regscale aws auth login')
|
|
491
|
+
2. Explicit credentials: --aws_access_key_id + --aws_secret_access_key
|
|
492
|
+
3. AWS profile: --profile
|
|
493
|
+
4. Environment variables or default AWS credential chain
|
|
153
494
|
"""
|
|
154
495
|
try:
|
|
155
496
|
from .inventory import collect_all_inventory
|
|
156
497
|
from regscale.models import DateTimeEncoder
|
|
157
498
|
|
|
158
499
|
logger.info("Collecting AWS inventory...")
|
|
500
|
+
|
|
501
|
+
# Parse tags
|
|
502
|
+
tag_dict = parse_tags(tags)
|
|
503
|
+
if tag_dict:
|
|
504
|
+
logger.info(f"Filtering resources by tags: {tag_dict}")
|
|
505
|
+
if account_id:
|
|
506
|
+
logger.info(f"Filtering resources by account ID: {account_id}")
|
|
507
|
+
|
|
508
|
+
# Resolve credentials from session cache or other methods
|
|
509
|
+
profile, aws_access_key_id, aws_secret_access_key, aws_session_token, region = resolve_aws_credentials(
|
|
510
|
+
session_name, profile, aws_access_key_id, aws_secret_access_key, aws_session_token, region
|
|
511
|
+
)
|
|
512
|
+
|
|
159
513
|
aws_inventory = collect_all_inventory(
|
|
160
514
|
region=region,
|
|
515
|
+
profile=profile,
|
|
161
516
|
aws_access_key_id=aws_access_key_id,
|
|
162
517
|
aws_secret_access_key=aws_secret_access_key,
|
|
163
518
|
aws_session_token=aws_session_token,
|
|
519
|
+
account_id=account_id,
|
|
520
|
+
tags=tag_dict,
|
|
164
521
|
)
|
|
165
522
|
logger.info(
|
|
166
523
|
"AWS inventory collected successfully. Received %s resource(s).",
|
|
@@ -274,53 +631,268 @@ def import_aws_scans(
|
|
|
274
631
|
help="RegScale will create and update findings as children of this record.",
|
|
275
632
|
required=True,
|
|
276
633
|
)
|
|
634
|
+
@click.option(
|
|
635
|
+
"--session-name",
|
|
636
|
+
type=str,
|
|
637
|
+
required=False,
|
|
638
|
+
help="Name of cached AWS session to use (from 'regscale aws auth login')",
|
|
639
|
+
)
|
|
640
|
+
@click.option(
|
|
641
|
+
"--profile",
|
|
642
|
+
type=str,
|
|
643
|
+
required=False,
|
|
644
|
+
help="AWS profile name from ~/.aws/credentials",
|
|
645
|
+
envvar="AWS_PROFILE",
|
|
646
|
+
)
|
|
277
647
|
@click.option(
|
|
278
648
|
"--aws_access_key_id",
|
|
279
649
|
type=str,
|
|
280
650
|
required=False,
|
|
281
|
-
help="AWS access key ID",
|
|
651
|
+
help="AWS access key ID (overrides profile)",
|
|
282
652
|
envvar="AWS_ACCESS_KEY_ID",
|
|
283
653
|
)
|
|
284
654
|
@click.option(
|
|
285
655
|
"--aws_secret_access_key",
|
|
286
656
|
type=str,
|
|
287
657
|
required=False,
|
|
288
|
-
help="AWS secret access key",
|
|
658
|
+
help="AWS secret access key (overrides profile)",
|
|
289
659
|
default=os.getenv("AWS_SECRET_ACCESS_KEY"),
|
|
290
660
|
)
|
|
291
661
|
@click.option(
|
|
292
662
|
"--aws_session_token",
|
|
293
663
|
type=click.STRING,
|
|
294
664
|
required=False,
|
|
295
|
-
help="AWS
|
|
665
|
+
help="AWS session token (overrides profile)",
|
|
296
666
|
default=os.environ.get("AWS_SESSION_TOKEN"),
|
|
297
667
|
)
|
|
668
|
+
@click.option(
|
|
669
|
+
"--generate-evidence",
|
|
670
|
+
is_flag=True,
|
|
671
|
+
default=False,
|
|
672
|
+
help="Generate evidence record for collected findings and link to SSP (uses --regscale_id)",
|
|
673
|
+
)
|
|
674
|
+
@click.option(
|
|
675
|
+
"--control-ids",
|
|
676
|
+
type=str,
|
|
677
|
+
default=None,
|
|
678
|
+
help="Comma-separated list of control IDs to link evidence (e.g., '123,456,789')",
|
|
679
|
+
)
|
|
680
|
+
@click.option(
|
|
681
|
+
"--format",
|
|
682
|
+
type=click.Choice(["native", "ocsf", "both"], case_sensitive=False),
|
|
683
|
+
default="native",
|
|
684
|
+
help="Output format for findings (native, ocsf, or both)",
|
|
685
|
+
)
|
|
686
|
+
@click.option(
|
|
687
|
+
"--account-id",
|
|
688
|
+
type=str,
|
|
689
|
+
required=False,
|
|
690
|
+
help="Filter findings by AWS account ID",
|
|
691
|
+
envvar="AWS_ACCOUNT_ID",
|
|
692
|
+
)
|
|
693
|
+
@click.option(
|
|
694
|
+
"--tags",
|
|
695
|
+
type=str,
|
|
696
|
+
required=False,
|
|
697
|
+
help="Filter findings by resource tags (format: key1=value1,key2=value2). All tags must match (AND logic).",
|
|
698
|
+
)
|
|
699
|
+
@click.option(
|
|
700
|
+
"--import-all-findings",
|
|
701
|
+
is_flag=True,
|
|
702
|
+
default=False,
|
|
703
|
+
help="Import all findings even if they are not associated with an asset in RegScale. By default, findings without matching assets are skipped.",
|
|
704
|
+
)
|
|
298
705
|
def sync_findings(
|
|
299
706
|
region: str,
|
|
300
707
|
regscale_id: int,
|
|
708
|
+
session_name: Optional[str] = None,
|
|
709
|
+
profile: Optional[str] = None,
|
|
301
710
|
aws_access_key_id: Optional[str] = None,
|
|
302
711
|
aws_secret_access_key: Optional[str] = None,
|
|
303
712
|
aws_session_token: Optional[str] = None,
|
|
713
|
+
generate_evidence: bool = False,
|
|
714
|
+
control_ids: Optional[str] = None,
|
|
715
|
+
format: str = "native",
|
|
716
|
+
account_id: Optional[str] = None,
|
|
717
|
+
tags: Optional[str] = None,
|
|
718
|
+
import_all_findings: bool = False,
|
|
304
719
|
) -> None:
|
|
305
720
|
"""
|
|
306
|
-
Sync AWS Security Hub findings to RegScale.
|
|
721
|
+
Sync AWS Security Hub findings to RegScale with optional filtering.
|
|
307
722
|
|
|
308
723
|
This command fetches findings from AWS Security Hub and creates/updates
|
|
309
|
-
corresponding issues in RegScale.
|
|
724
|
+
corresponding issues in RegScale. Optionally generates evidence records
|
|
725
|
+
and supports OCSF (Open Cybersecurity Schema Framework) format export.
|
|
726
|
+
|
|
727
|
+
Filtering Options:
|
|
728
|
+
Use --account-id to filter findings by AWS account.
|
|
729
|
+
Use --tags to filter findings by resource tags (format: key1=value1,key2=value2).
|
|
730
|
+
Both filters use AND logic - all criteria must match.
|
|
731
|
+
|
|
732
|
+
Evidence Generation:
|
|
733
|
+
Use --generate-evidence to create evidence records for compliance documentation.
|
|
734
|
+
Evidence will be automatically linked to the SSP specified by --regscale_id.
|
|
735
|
+
Optionally link to specific controls with --control-ids.
|
|
736
|
+
|
|
737
|
+
OCSF Format:
|
|
738
|
+
Use --format ocsf to export findings in OCSF normalized format for cross-platform
|
|
739
|
+
security data sharing. Use --format both for dual-format export.
|
|
740
|
+
|
|
741
|
+
Authentication methods (in priority order):
|
|
742
|
+
1. Cached session: --session-name (from 'regscale aws auth login')
|
|
743
|
+
2. Explicit credentials: --aws_access_key_id + --aws_secret_access_key
|
|
744
|
+
3. AWS profile: --profile
|
|
745
|
+
4. Environment variables or default AWS credential chain
|
|
746
|
+
"""
|
|
747
|
+
# Parse tags into dictionary
|
|
748
|
+
tag_dict = parse_tags(tags) if tags else None
|
|
749
|
+
|
|
750
|
+
# Create credential config
|
|
751
|
+
credentials = AWSCredentialConfig(
|
|
752
|
+
session_name=session_name,
|
|
753
|
+
profile=profile,
|
|
754
|
+
aws_access_key_id=aws_access_key_id,
|
|
755
|
+
aws_secret_access_key=aws_secret_access_key,
|
|
756
|
+
aws_session_token=aws_session_token,
|
|
757
|
+
account_id=account_id,
|
|
758
|
+
tags=tag_dict,
|
|
759
|
+
)
|
|
760
|
+
|
|
761
|
+
# Create config object
|
|
762
|
+
config = FindingsSyncConfig(
|
|
763
|
+
region=region,
|
|
764
|
+
regscale_id=regscale_id,
|
|
765
|
+
credentials=credentials,
|
|
766
|
+
generate_evidence=generate_evidence,
|
|
767
|
+
control_ids=control_ids,
|
|
768
|
+
format=format,
|
|
769
|
+
import_all_findings=import_all_findings,
|
|
770
|
+
)
|
|
771
|
+
_sync_findings_with_config(config)
|
|
772
|
+
|
|
773
|
+
|
|
774
|
+
def _sync_findings_with_config(config: FindingsSyncConfig) -> None:
|
|
775
|
+
"""
|
|
776
|
+
Internal function to sync findings using configuration object.
|
|
777
|
+
|
|
778
|
+
:param FindingsSyncConfig config: Configuration for findings sync
|
|
310
779
|
"""
|
|
311
780
|
try:
|
|
312
781
|
logger.info("Starting AWS Security Hub findings sync to RegScale...")
|
|
313
782
|
from .scanner import AWSInventoryIntegration
|
|
783
|
+
import boto3
|
|
784
|
+
from regscale.integrations.commercial.aws.common import fetch_aws_findings
|
|
314
785
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
aws_access_key_id
|
|
320
|
-
aws_secret_access_key
|
|
321
|
-
aws_session_token
|
|
786
|
+
# Resolve credentials from session cache or other methods
|
|
787
|
+
profile, aws_access_key_id, aws_secret_access_key, aws_session_token, region = resolve_aws_credentials(
|
|
788
|
+
config.credentials.session_name,
|
|
789
|
+
config.credentials.profile,
|
|
790
|
+
config.credentials.aws_access_key_id,
|
|
791
|
+
config.credentials.aws_secret_access_key,
|
|
792
|
+
config.credentials.aws_session_token,
|
|
793
|
+
config.region,
|
|
322
794
|
)
|
|
323
|
-
|
|
795
|
+
|
|
796
|
+
# Debug logging
|
|
797
|
+
logger.debug(
|
|
798
|
+
f"Resolved credentials - profile: {profile}, region: {region}, has_keys: {bool(aws_access_key_id)}"
|
|
799
|
+
)
|
|
800
|
+
|
|
801
|
+
# Get tag dictionary and account ID from credentials
|
|
802
|
+
tag_dict = config.credentials.tags
|
|
803
|
+
if tag_dict:
|
|
804
|
+
logger.info(f"Filtering findings by tags: {tag_dict}")
|
|
805
|
+
if config.credentials.account_id:
|
|
806
|
+
logger.info(f"Filtering findings by account ID: {config.credentials.account_id}")
|
|
807
|
+
|
|
808
|
+
# Parse control IDs
|
|
809
|
+
control_id_list = None
|
|
810
|
+
if config.control_ids:
|
|
811
|
+
control_id_list = [int(cid.strip()) for cid in config.control_ids.split(",")]
|
|
812
|
+
|
|
813
|
+
# If evidence generation or OCSF format requested, use enhanced processing
|
|
814
|
+
if config.generate_evidence or config.format != "native":
|
|
815
|
+
# Create AWS session
|
|
816
|
+
if aws_access_key_id or aws_secret_access_key:
|
|
817
|
+
session = boto3.Session(
|
|
818
|
+
region_name=region,
|
|
819
|
+
aws_access_key_id=aws_access_key_id,
|
|
820
|
+
aws_secret_access_key=aws_secret_access_key,
|
|
821
|
+
aws_session_token=aws_session_token,
|
|
822
|
+
)
|
|
823
|
+
else:
|
|
824
|
+
session = boto3.Session(
|
|
825
|
+
profile_name=profile,
|
|
826
|
+
region_name=region,
|
|
827
|
+
)
|
|
828
|
+
client = session.client("securityhub")
|
|
829
|
+
|
|
830
|
+
logger.info("Fetching findings from AWS Security Hub...")
|
|
831
|
+
# Fetch raw findings with minimum severity from config
|
|
832
|
+
from regscale.core.app.application import Application
|
|
833
|
+
|
|
834
|
+
app = Application()
|
|
835
|
+
minimum_severity = app.config.get("issues", {}).get("amazon", {}).get("minimumSeverity")
|
|
836
|
+
raw_findings = fetch_aws_findings(aws_client=client, minimum_severity=minimum_severity)
|
|
837
|
+
logger.info(f"Fetched {len(raw_findings)} findings from AWS Security Hub")
|
|
838
|
+
|
|
839
|
+
# Process with evidence/OCSF support
|
|
840
|
+
scanner = AWSInventoryIntegration(
|
|
841
|
+
plan_id=config.regscale_id, import_all_findings=config.import_all_findings
|
|
842
|
+
)
|
|
843
|
+
scanner.authenticate(
|
|
844
|
+
aws_access_key_id=aws_access_key_id,
|
|
845
|
+
aws_secret_access_key=aws_secret_access_key,
|
|
846
|
+
region=region,
|
|
847
|
+
aws_session_token=aws_session_token,
|
|
848
|
+
profile=profile,
|
|
849
|
+
account_id=config.credentials.account_id,
|
|
850
|
+
tags=tag_dict,
|
|
851
|
+
)
|
|
852
|
+
|
|
853
|
+
# Don't set num_findings_to_process since many findings get filtered during parse_finding()
|
|
854
|
+
# Progress bar will show processed count without a misleading total
|
|
855
|
+
|
|
856
|
+
# Get asset map for linking findings to assets
|
|
857
|
+
logger.info("Loading asset map from RegScale...")
|
|
858
|
+
scanner.asset_map_by_identifier.update(scanner.get_asset_map())
|
|
859
|
+
|
|
860
|
+
logger.info("Processing findings with evidence/OCSF support...")
|
|
861
|
+
integration_findings, evidence = scanner.process_findings_with_evidence(
|
|
862
|
+
findings=raw_findings,
|
|
863
|
+
service_name="SecurityHub",
|
|
864
|
+
generate_evidence=config.generate_evidence,
|
|
865
|
+
ssp_id=config.regscale_id, # Use regscale_id as the SSP ID for evidence linking
|
|
866
|
+
control_ids=control_id_list,
|
|
867
|
+
ocsf_format=(config.format in ["ocsf", "both"]),
|
|
868
|
+
)
|
|
869
|
+
|
|
870
|
+
# Sync findings to RegScale with progress bar
|
|
871
|
+
logger.info("Syncing findings to RegScale...")
|
|
872
|
+
findings_processed = scanner.update_regscale_findings(integration_findings)
|
|
873
|
+
|
|
874
|
+
logger.info(
|
|
875
|
+
f"AWS Security Hub findings sync completed successfully. Processed {findings_processed} findings."
|
|
876
|
+
)
|
|
877
|
+
if evidence:
|
|
878
|
+
logger.info(f"Created evidence record: {evidence.id} - {evidence.title}")
|
|
879
|
+
else:
|
|
880
|
+
# Standard sync without evidence generation
|
|
881
|
+
findings_processed = AWSInventoryIntegration.sync_findings(
|
|
882
|
+
plan_id=config.regscale_id,
|
|
883
|
+
region=region,
|
|
884
|
+
profile=profile,
|
|
885
|
+
aws_access_key_id=aws_access_key_id,
|
|
886
|
+
aws_secret_access_key=aws_secret_access_key,
|
|
887
|
+
aws_session_token=aws_session_token,
|
|
888
|
+
account_id=config.credentials.account_id,
|
|
889
|
+
tags=tag_dict,
|
|
890
|
+
import_all_findings=config.import_all_findings,
|
|
891
|
+
)
|
|
892
|
+
logger.info(
|
|
893
|
+
f"AWS Security Hub findings sync completed successfully. Processed {findings_processed} findings."
|
|
894
|
+
)
|
|
895
|
+
|
|
324
896
|
except Exception as e:
|
|
325
897
|
logger.error(f"Error syncing AWS Security Hub findings: {e}", exc_info=True)
|
|
326
898
|
raise click.ClickException(str(e))
|
|
@@ -340,33 +912,69 @@ def sync_findings(
|
|
|
340
912
|
help="RegScale will create and update findings and assets as children of this record.",
|
|
341
913
|
required=True,
|
|
342
914
|
)
|
|
915
|
+
@click.option(
|
|
916
|
+
"--session-name",
|
|
917
|
+
type=str,
|
|
918
|
+
required=False,
|
|
919
|
+
help="Name of cached AWS session to use (from 'regscale aws auth login')",
|
|
920
|
+
)
|
|
921
|
+
@click.option(
|
|
922
|
+
"--profile",
|
|
923
|
+
type=str,
|
|
924
|
+
required=False,
|
|
925
|
+
help="AWS profile name from ~/.aws/credentials",
|
|
926
|
+
envvar="AWS_PROFILE",
|
|
927
|
+
)
|
|
343
928
|
@click.option(
|
|
344
929
|
"--aws_access_key_id",
|
|
345
930
|
type=str,
|
|
346
931
|
required=False,
|
|
347
|
-
help="AWS access key ID",
|
|
932
|
+
help="AWS access key ID (overrides profile)",
|
|
348
933
|
envvar="AWS_ACCESS_KEY_ID",
|
|
349
934
|
)
|
|
350
935
|
@click.option(
|
|
351
936
|
"--aws_secret_access_key",
|
|
352
937
|
type=str,
|
|
353
938
|
required=False,
|
|
354
|
-
help="AWS secret access key",
|
|
939
|
+
help="AWS secret access key (overrides profile)",
|
|
355
940
|
default=os.getenv("AWS_SECRET_ACCESS_KEY"),
|
|
356
941
|
)
|
|
357
942
|
@click.option(
|
|
358
943
|
"--aws_session_token",
|
|
359
944
|
type=click.STRING,
|
|
360
945
|
required=False,
|
|
361
|
-
help="AWS
|
|
946
|
+
help="AWS session token (overrides profile)",
|
|
362
947
|
default=os.environ.get("AWS_SESSION_TOKEN"),
|
|
363
948
|
)
|
|
949
|
+
@click.option(
|
|
950
|
+
"--account-id",
|
|
951
|
+
type=str,
|
|
952
|
+
required=False,
|
|
953
|
+
help="Filter resources by AWS account ID (from ARN)",
|
|
954
|
+
)
|
|
955
|
+
@click.option(
|
|
956
|
+
"--tags",
|
|
957
|
+
type=str,
|
|
958
|
+
required=False,
|
|
959
|
+
help="Filter resources by tags (format: key1=value1,key2=value2). All specified tags must match (AND logic).",
|
|
960
|
+
)
|
|
961
|
+
@click.option(
|
|
962
|
+
"--import-all-findings",
|
|
963
|
+
is_flag=True,
|
|
964
|
+
default=False,
|
|
965
|
+
help="Import all findings even if they are not associated with an asset in RegScale. By default, findings without matching assets are skipped.",
|
|
966
|
+
)
|
|
364
967
|
def sync_findings_and_assets(
|
|
365
968
|
region: str,
|
|
366
969
|
regscale_id: int,
|
|
970
|
+
session_name: Optional[str] = None,
|
|
971
|
+
profile: Optional[str] = None,
|
|
367
972
|
aws_access_key_id: Optional[str] = None,
|
|
368
973
|
aws_secret_access_key: Optional[str] = None,
|
|
369
974
|
aws_session_token: Optional[str] = None,
|
|
975
|
+
account_id: Optional[str] = None,
|
|
976
|
+
tags: Optional[str] = None,
|
|
977
|
+
import_all_findings: bool = False,
|
|
370
978
|
) -> None:
|
|
371
979
|
"""
|
|
372
980
|
Sync AWS Security Hub findings and automatically discovered assets to RegScale.
|
|
@@ -375,18 +983,43 @@ def sync_findings_and_assets(
|
|
|
375
983
|
issues in RegScale, and also creates assets for the resources referenced in the findings.
|
|
376
984
|
This provides a comprehensive view by creating both the security findings and the
|
|
377
985
|
underlying AWS resources they reference.
|
|
986
|
+
|
|
987
|
+
Filtering support:
|
|
988
|
+
- Account ID: Filter resources by AWS account ID (extracted from ARNs)
|
|
989
|
+
- Tags: Filter resources by tag key-value pairs (AND logic - all must match)
|
|
990
|
+
|
|
991
|
+
Authentication methods (in priority order):
|
|
992
|
+
1. Cached session: --session-name (from 'regscale aws auth login')
|
|
993
|
+
2. Explicit credentials: --aws_access_key_id + --aws_secret_access_key
|
|
994
|
+
3. AWS profile: --profile
|
|
995
|
+
4. Environment variables or default AWS credential chain
|
|
378
996
|
"""
|
|
379
997
|
try:
|
|
380
998
|
logger.info("Starting AWS Security Hub findings and assets sync to RegScale...")
|
|
381
999
|
from .scanner import AWSInventoryIntegration
|
|
382
1000
|
|
|
383
|
-
|
|
1001
|
+
# Parse tags if provided
|
|
1002
|
+
tag_dict = parse_tags(tags)
|
|
1003
|
+
if account_id:
|
|
1004
|
+
logger.info(f"Filtering resources by account ID: {account_id}")
|
|
1005
|
+
if tag_dict:
|
|
1006
|
+
logger.info(f"Filtering resources by tags: {tag_dict}")
|
|
1007
|
+
|
|
1008
|
+
# Resolve credentials from session cache or other methods
|
|
1009
|
+
profile, aws_access_key_id, aws_secret_access_key, aws_session_token, region = resolve_aws_credentials(
|
|
1010
|
+
session_name, profile, aws_access_key_id, aws_secret_access_key, aws_session_token, region
|
|
1011
|
+
)
|
|
1012
|
+
|
|
1013
|
+
scanner = AWSInventoryIntegration(plan_id=regscale_id, import_all_findings=import_all_findings)
|
|
384
1014
|
findings_processed, assets_processed = scanner.sync_findings_and_assets(
|
|
385
1015
|
plan_id=regscale_id,
|
|
386
1016
|
region=region,
|
|
1017
|
+
profile=profile,
|
|
387
1018
|
aws_access_key_id=aws_access_key_id,
|
|
388
1019
|
aws_secret_access_key=aws_secret_access_key,
|
|
389
1020
|
aws_session_token=aws_session_token,
|
|
1021
|
+
account_id=account_id,
|
|
1022
|
+
tags=tag_dict,
|
|
390
1023
|
)
|
|
391
1024
|
logger.info(
|
|
392
1025
|
f"AWS Security Hub sync completed successfully. "
|
|
@@ -403,6 +1036,121 @@ def findings():
|
|
|
403
1036
|
pass
|
|
404
1037
|
|
|
405
1038
|
|
|
1039
|
+
def _create_aws_session(
|
|
1040
|
+
aws_access_key_id: Optional[str],
|
|
1041
|
+
aws_secret_access_key: Optional[str],
|
|
1042
|
+
aws_session_token: Optional[str],
|
|
1043
|
+
profile: Optional[str],
|
|
1044
|
+
region: str,
|
|
1045
|
+
) -> "boto3.Session":
|
|
1046
|
+
"""
|
|
1047
|
+
Create boto3 session with provided credentials.
|
|
1048
|
+
|
|
1049
|
+
:param Optional[str] aws_access_key_id: AWS access key ID
|
|
1050
|
+
:param Optional[str] aws_secret_access_key: AWS secret access key
|
|
1051
|
+
:param Optional[str] aws_session_token: AWS session token
|
|
1052
|
+
:param Optional[str] profile: AWS profile name
|
|
1053
|
+
:param str region: AWS region
|
|
1054
|
+
:return: Configured boto3 session
|
|
1055
|
+
:rtype: boto3.Session
|
|
1056
|
+
"""
|
|
1057
|
+
import boto3
|
|
1058
|
+
|
|
1059
|
+
if aws_access_key_id or aws_secret_access_key:
|
|
1060
|
+
return boto3.Session(
|
|
1061
|
+
region_name=region,
|
|
1062
|
+
aws_access_key_id=aws_access_key_id,
|
|
1063
|
+
aws_secret_access_key=aws_secret_access_key,
|
|
1064
|
+
aws_session_token=aws_session_token,
|
|
1065
|
+
)
|
|
1066
|
+
return boto3.Session(profile_name=profile, region_name=region)
|
|
1067
|
+
|
|
1068
|
+
|
|
1069
|
+
def _finding_matches_account(finding: Dict[str, Any], account_id: str) -> bool:
|
|
1070
|
+
"""
|
|
1071
|
+
Check if finding's resources match the account ID.
|
|
1072
|
+
|
|
1073
|
+
:param Dict[str, Any] finding: AWS Security Hub finding
|
|
1074
|
+
:param str account_id: AWS account ID to match
|
|
1075
|
+
:return: True if any resource matches the account ID
|
|
1076
|
+
:rtype: bool
|
|
1077
|
+
"""
|
|
1078
|
+
resources = finding.get("Resources", [])
|
|
1079
|
+
for resource in resources:
|
|
1080
|
+
resource_id = resource.get("Id", "")
|
|
1081
|
+
if resource_id.startswith("arn:"):
|
|
1082
|
+
arn_parts = resource_id.split(":")
|
|
1083
|
+
if len(arn_parts) >= 5 and arn_parts[4] == account_id:
|
|
1084
|
+
return True
|
|
1085
|
+
return False
|
|
1086
|
+
|
|
1087
|
+
|
|
1088
|
+
def _finding_matches_tags(finding: Dict[str, Any], tag_dict: Dict[str, str]) -> bool:
|
|
1089
|
+
"""
|
|
1090
|
+
Check if finding's resources match all specified tags.
|
|
1091
|
+
|
|
1092
|
+
:param Dict[str, Any] finding: AWS Security Hub finding
|
|
1093
|
+
:param Dict[str, str] tag_dict: Tags to match (all must match - AND logic)
|
|
1094
|
+
:return: True if any resource matches all tags
|
|
1095
|
+
:rtype: bool
|
|
1096
|
+
"""
|
|
1097
|
+
resources = finding.get("Resources", [])
|
|
1098
|
+
for resource in resources:
|
|
1099
|
+
resource_tags = resource.get("Tags", {})
|
|
1100
|
+
if all(resource_tags.get(k) == v for k, v in tag_dict.items()):
|
|
1101
|
+
return True
|
|
1102
|
+
return False
|
|
1103
|
+
|
|
1104
|
+
|
|
1105
|
+
def _filter_findings(
|
|
1106
|
+
findings: List[Dict[str, Any]], account_id: Optional[str], tag_dict: Optional[Dict[str, str]]
|
|
1107
|
+
) -> List[Dict[str, Any]]:
|
|
1108
|
+
"""
|
|
1109
|
+
Apply account and tag filters to findings.
|
|
1110
|
+
|
|
1111
|
+
:param List[Dict[str, Any]] findings: List of findings to filter
|
|
1112
|
+
:param Optional[str] account_id: Account ID filter (if specified)
|
|
1113
|
+
:param Optional[Dict[str, str]] tag_dict: Tag filters (if specified)
|
|
1114
|
+
:return: Filtered list of findings
|
|
1115
|
+
:rtype: List[Dict[str, Any]]
|
|
1116
|
+
"""
|
|
1117
|
+
if not account_id and not tag_dict:
|
|
1118
|
+
return findings
|
|
1119
|
+
|
|
1120
|
+
filtered = []
|
|
1121
|
+
for finding in findings:
|
|
1122
|
+
if account_id and not _finding_matches_account(finding, account_id):
|
|
1123
|
+
continue
|
|
1124
|
+
if tag_dict and not _finding_matches_tags(finding, tag_dict):
|
|
1125
|
+
continue
|
|
1126
|
+
filtered.append(finding)
|
|
1127
|
+
|
|
1128
|
+
return filtered
|
|
1129
|
+
|
|
1130
|
+
|
|
1131
|
+
def _write_findings_output(findings: List[Dict[str, Any]], output: Optional[str]) -> None:
|
|
1132
|
+
"""
|
|
1133
|
+
Write findings to stdout or file.
|
|
1134
|
+
|
|
1135
|
+
:param List[Dict[str, Any]] findings: Findings to write
|
|
1136
|
+
:param Optional[str] output: Output path (None for default, '-' for stdout)
|
|
1137
|
+
:rtype: None
|
|
1138
|
+
"""
|
|
1139
|
+
from regscale.models import DateTimeEncoder
|
|
1140
|
+
|
|
1141
|
+
# Default output path
|
|
1142
|
+
if output is None:
|
|
1143
|
+
output = os.path.join("artifacts", "aws", "findings.json")
|
|
1144
|
+
|
|
1145
|
+
if output == "-":
|
|
1146
|
+
click.echo(json.dumps(findings, indent=2, cls=DateTimeEncoder))
|
|
1147
|
+
else:
|
|
1148
|
+
os.makedirs(os.path.dirname(output), exist_ok=True)
|
|
1149
|
+
with open(output, "w", encoding="utf-8") as f:
|
|
1150
|
+
json.dump(findings, f, indent=2, cls=DateTimeEncoder)
|
|
1151
|
+
logger.info(f"Findings saved to {output}")
|
|
1152
|
+
|
|
1153
|
+
|
|
406
1154
|
@findings.command(name="collect")
|
|
407
1155
|
@click.option(
|
|
408
1156
|
"--region",
|
|
@@ -410,27 +1158,52 @@ def findings():
|
|
|
410
1158
|
default=os.getenv("AWS_REGION", "us-east-1"),
|
|
411
1159
|
help="AWS region to collect findings from. Default is us-east-1.",
|
|
412
1160
|
)
|
|
1161
|
+
@click.option(
|
|
1162
|
+
"--session-name",
|
|
1163
|
+
type=str,
|
|
1164
|
+
required=False,
|
|
1165
|
+
help="Name of cached AWS session to use (from 'regscale aws auth login')",
|
|
1166
|
+
)
|
|
1167
|
+
@click.option(
|
|
1168
|
+
"--profile",
|
|
1169
|
+
type=str,
|
|
1170
|
+
required=False,
|
|
1171
|
+
help="AWS profile name from ~/.aws/credentials",
|
|
1172
|
+
envvar="AWS_PROFILE",
|
|
1173
|
+
)
|
|
413
1174
|
@click.option(
|
|
414
1175
|
"--aws_access_key_id",
|
|
415
1176
|
type=str,
|
|
416
1177
|
required=False,
|
|
417
|
-
help="AWS access key ID",
|
|
1178
|
+
help="AWS access key ID (overrides profile)",
|
|
418
1179
|
envvar="AWS_ACCESS_KEY_ID",
|
|
419
1180
|
)
|
|
420
1181
|
@click.option(
|
|
421
1182
|
"--aws_secret_access_key",
|
|
422
1183
|
type=str,
|
|
423
1184
|
required=False,
|
|
424
|
-
help="AWS secret access key",
|
|
1185
|
+
help="AWS secret access key (overrides profile)",
|
|
425
1186
|
envvar="AWS_SECRET_ACCESS_KEY",
|
|
426
1187
|
)
|
|
427
1188
|
@click.option(
|
|
428
1189
|
"--aws_session_token",
|
|
429
1190
|
type=click.STRING,
|
|
430
1191
|
required=False,
|
|
431
|
-
help="AWS
|
|
1192
|
+
help="AWS session token (overrides profile)",
|
|
432
1193
|
default=os.environ.get("AWS_SESSION_TOKEN"),
|
|
433
1194
|
)
|
|
1195
|
+
@click.option(
|
|
1196
|
+
"--account-id",
|
|
1197
|
+
type=str,
|
|
1198
|
+
required=False,
|
|
1199
|
+
help="Filter findings by AWS account ID (from ARN)",
|
|
1200
|
+
)
|
|
1201
|
+
@click.option(
|
|
1202
|
+
"--tags",
|
|
1203
|
+
type=str,
|
|
1204
|
+
required=False,
|
|
1205
|
+
help="Filter findings by tags (format: key1=value1,key2=value2). All specified tags must match (AND logic).",
|
|
1206
|
+
)
|
|
434
1207
|
@click.option(
|
|
435
1208
|
"--output",
|
|
436
1209
|
type=click.Path(dir_okay=False, writable=True),
|
|
@@ -439,9 +1212,13 @@ def findings():
|
|
|
439
1212
|
)
|
|
440
1213
|
def collect_findings(
|
|
441
1214
|
region: str,
|
|
1215
|
+
session_name: Optional[str],
|
|
1216
|
+
profile: Optional[str],
|
|
442
1217
|
aws_access_key_id: Optional[str],
|
|
443
1218
|
aws_secret_access_key: Optional[str],
|
|
444
1219
|
aws_session_token: Optional[str],
|
|
1220
|
+
account_id: Optional[str],
|
|
1221
|
+
tags: Optional[str],
|
|
445
1222
|
output: Optional[str],
|
|
446
1223
|
) -> None:
|
|
447
1224
|
"""
|
|
@@ -453,44 +1230,2320 @@ def collect_findings(
|
|
|
453
1230
|
|
|
454
1231
|
If no output file is specified, findings will be saved to artifacts/aws/findings.json
|
|
455
1232
|
by default. Use --output - to display to stdout instead.
|
|
1233
|
+
|
|
1234
|
+
Filtering support:
|
|
1235
|
+
- Account ID: Filter findings by AWS account ID (extracted from ARNs)
|
|
1236
|
+
- Tags: Filter findings by tag key-value pairs (AND logic - all must match)
|
|
1237
|
+
|
|
1238
|
+
Authentication methods (in priority order):
|
|
1239
|
+
1. Cached session: --session-name (from 'regscale aws auth login')
|
|
1240
|
+
2. Explicit credentials: --aws_access_key_id + --aws_secret_access_key
|
|
1241
|
+
3. AWS profile: --profile
|
|
1242
|
+
4. Environment variables or default AWS credential chain
|
|
456
1243
|
"""
|
|
457
1244
|
try:
|
|
458
|
-
import
|
|
459
|
-
from regscale.integrations.commercial.amazon.common import fetch_aws_findings
|
|
460
|
-
from regscale.models import DateTimeEncoder
|
|
1245
|
+
from regscale.integrations.commercial.aws.common import fetch_aws_findings
|
|
461
1246
|
|
|
462
1247
|
logger.info("Collecting AWS Security Hub findings...")
|
|
463
1248
|
|
|
464
|
-
#
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
1249
|
+
# Parse tags and log filters
|
|
1250
|
+
tag_dict = parse_tags(tags)
|
|
1251
|
+
if account_id:
|
|
1252
|
+
logger.info(f"Filtering findings by account ID: {account_id}")
|
|
1253
|
+
if tag_dict:
|
|
1254
|
+
logger.info(f"Filtering findings by tags: {tag_dict}")
|
|
1255
|
+
|
|
1256
|
+
# Resolve credentials
|
|
1257
|
+
profile, aws_access_key_id, aws_secret_access_key, aws_session_token, region = resolve_aws_credentials(
|
|
1258
|
+
session_name, profile, aws_access_key_id, aws_secret_access_key, aws_session_token, region
|
|
470
1259
|
)
|
|
471
|
-
client = session.client("securityhub")
|
|
472
1260
|
|
|
473
|
-
#
|
|
1261
|
+
# Create session and fetch findings
|
|
1262
|
+
session = _create_aws_session(aws_access_key_id, aws_secret_access_key, aws_session_token, profile, region)
|
|
1263
|
+
client = session.client("securityhub")
|
|
474
1264
|
findings = fetch_aws_findings(aws_client=client)
|
|
475
1265
|
|
|
1266
|
+
# Apply filtering
|
|
1267
|
+
original_count = len(findings)
|
|
1268
|
+
findings = _filter_findings(findings, account_id, tag_dict)
|
|
1269
|
+
|
|
1270
|
+
if account_id or tag_dict:
|
|
1271
|
+
logger.info(f"Filtered from {original_count} to {len(findings)} findings based on criteria")
|
|
1272
|
+
|
|
476
1273
|
logger.info(f"AWS Security Hub findings collected successfully. Found {len(findings)} finding(s).")
|
|
477
1274
|
|
|
478
|
-
#
|
|
479
|
-
|
|
480
|
-
|
|
1275
|
+
# Write output
|
|
1276
|
+
_write_findings_output(findings, output)
|
|
1277
|
+
|
|
1278
|
+
except Exception as e:
|
|
1279
|
+
logger.error(f"Error collecting AWS Security Hub findings: {e}", exc_info=True)
|
|
1280
|
+
raise click.ClickException(str(e))
|
|
1281
|
+
|
|
1282
|
+
|
|
1283
|
+
@awsv2.group()
|
|
1284
|
+
def auth():
|
|
1285
|
+
"""AWS session token management commands."""
|
|
1286
|
+
pass
|
|
1287
|
+
|
|
1288
|
+
|
|
1289
|
+
@auth.command(name="login")
|
|
1290
|
+
@click.option(
|
|
1291
|
+
"--session-name",
|
|
1292
|
+
type=str,
|
|
1293
|
+
required=True,
|
|
1294
|
+
help="Name for this session (used to cache credentials)",
|
|
1295
|
+
)
|
|
1296
|
+
@click.option(
|
|
1297
|
+
"--profile",
|
|
1298
|
+
type=str,
|
|
1299
|
+
required=False,
|
|
1300
|
+
help="AWS profile name from ~/.aws/credentials",
|
|
1301
|
+
envvar="AWS_PROFILE",
|
|
1302
|
+
)
|
|
1303
|
+
@click.option(
|
|
1304
|
+
"--aws_access_key_id",
|
|
1305
|
+
type=str,
|
|
1306
|
+
required=False,
|
|
1307
|
+
help="AWS access key ID",
|
|
1308
|
+
envvar="AWS_ACCESS_KEY_ID",
|
|
1309
|
+
)
|
|
1310
|
+
@click.option(
|
|
1311
|
+
"--aws_secret_access_key",
|
|
1312
|
+
type=str,
|
|
1313
|
+
required=False,
|
|
1314
|
+
help="AWS secret access key",
|
|
1315
|
+
envvar="AWS_SECRET_ACCESS_KEY",
|
|
1316
|
+
)
|
|
1317
|
+
@click.option(
|
|
1318
|
+
"--mfa-serial",
|
|
1319
|
+
type=str,
|
|
1320
|
+
required=False,
|
|
1321
|
+
help="ARN of MFA device (e.g., arn:aws:iam::123456789012:mfa/username)",
|
|
1322
|
+
)
|
|
1323
|
+
@click.option(
|
|
1324
|
+
"--mfa-code",
|
|
1325
|
+
type=str,
|
|
1326
|
+
required=False,
|
|
1327
|
+
help="6-digit MFA code from authenticator app",
|
|
1328
|
+
)
|
|
1329
|
+
@click.option(
|
|
1330
|
+
"--role-arn",
|
|
1331
|
+
type=str,
|
|
1332
|
+
required=False,
|
|
1333
|
+
help="ARN of role to assume (e.g., arn:aws:iam::123456789012:role/MyRole)",
|
|
1334
|
+
)
|
|
1335
|
+
@click.option(
|
|
1336
|
+
"--role-session-name",
|
|
1337
|
+
type=str,
|
|
1338
|
+
required=False,
|
|
1339
|
+
help="Name for the assumed role session",
|
|
1340
|
+
)
|
|
1341
|
+
@click.option(
|
|
1342
|
+
"--duration",
|
|
1343
|
+
type=int,
|
|
1344
|
+
default=3600,
|
|
1345
|
+
help="Duration for session token in seconds (900-43200, default: 3600)",
|
|
1346
|
+
)
|
|
1347
|
+
@click.option(
|
|
1348
|
+
"--region",
|
|
1349
|
+
type=str,
|
|
1350
|
+
default=os.environ.get("AWS_REGION", "us-east-1"),
|
|
1351
|
+
help="AWS region to associate with this session",
|
|
1352
|
+
)
|
|
1353
|
+
def login(
|
|
1354
|
+
session_name: str,
|
|
1355
|
+
profile: Optional[str],
|
|
1356
|
+
aws_access_key_id: Optional[str],
|
|
1357
|
+
aws_secret_access_key: Optional[str],
|
|
1358
|
+
mfa_serial: Optional[str],
|
|
1359
|
+
mfa_code: Optional[str],
|
|
1360
|
+
role_arn: Optional[str],
|
|
1361
|
+
role_session_name: Optional[str],
|
|
1362
|
+
duration: int,
|
|
1363
|
+
region: str,
|
|
1364
|
+
) -> None:
|
|
1365
|
+
"""
|
|
1366
|
+
Generate and cache AWS session tokens.
|
|
1367
|
+
|
|
1368
|
+
This command generates temporary AWS credentials (session tokens) and caches them
|
|
1369
|
+
locally for use with subsequent AWS commands. Session tokens provide better security
|
|
1370
|
+
than long-term access keys and support MFA authentication.
|
|
1371
|
+
|
|
1372
|
+
Examples:
|
|
1373
|
+
|
|
1374
|
+
# Simple session from profile
|
|
1375
|
+
regscale aws auth login --session-name my-session --profile default
|
|
1376
|
+
|
|
1377
|
+
# Session with MFA
|
|
1378
|
+
regscale aws auth login --session-name my-session --profile default \\
|
|
1379
|
+
--mfa-serial arn:aws:iam::123456789012:mfa/username --mfa-code 123456
|
|
1380
|
+
|
|
1381
|
+
# Assume role with MFA
|
|
1382
|
+
regscale aws auth login --session-name cross-account --profile default \\
|
|
1383
|
+
--role-arn arn:aws:iam::987654321098:role/CrossAccountRole \\
|
|
1384
|
+
--mfa-serial arn:aws:iam::123456789012:mfa/username --mfa-code 123456
|
|
1385
|
+
|
|
1386
|
+
# Session with explicit credentials
|
|
1387
|
+
regscale aws auth login --session-name my-session \\
|
|
1388
|
+
--aws_access_key_id AKIAIOSFODNN7EXAMPLE \\
|
|
1389
|
+
--aws_secret_access_key wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
|
|
1390
|
+
"""
|
|
1391
|
+
try:
|
|
1392
|
+
from .session_manager import AWSSessionManager
|
|
1393
|
+
|
|
1394
|
+
logger.info(f"Generating AWS session token: {session_name}")
|
|
1395
|
+
|
|
1396
|
+
manager = AWSSessionManager()
|
|
1397
|
+
|
|
1398
|
+
# Generate session token
|
|
1399
|
+
credentials = manager.get_session_token(
|
|
1400
|
+
profile=profile,
|
|
1401
|
+
aws_access_key_id=aws_access_key_id,
|
|
1402
|
+
aws_secret_access_key=aws_secret_access_key,
|
|
1403
|
+
mfa_serial=mfa_serial,
|
|
1404
|
+
mfa_code=mfa_code,
|
|
1405
|
+
role_arn=role_arn,
|
|
1406
|
+
role_session_name=role_session_name,
|
|
1407
|
+
duration_seconds=duration,
|
|
1408
|
+
)
|
|
1409
|
+
|
|
1410
|
+
# Cache the session
|
|
1411
|
+
manager.cache_session(session_name, credentials, region)
|
|
1412
|
+
|
|
1413
|
+
click.echo(click.style(f"\n✓ Session '{session_name}' created successfully!", fg="green", bold=True))
|
|
1414
|
+
click.echo(f"Region: {region}")
|
|
1415
|
+
click.echo(f"Expires: {credentials['expiration']}")
|
|
1416
|
+
click.echo(f"\nUse --session-name {session_name} with AWS commands to use these credentials.")
|
|
1417
|
+
|
|
1418
|
+
except Exception as e:
|
|
1419
|
+
logger.error(f"Failed to generate session token: {e}", exc_info=True)
|
|
1420
|
+
raise click.ClickException(str(e))
|
|
1421
|
+
|
|
1422
|
+
|
|
1423
|
+
@auth.command(name="logout")
|
|
1424
|
+
@click.option(
|
|
1425
|
+
"--session-name",
|
|
1426
|
+
type=str,
|
|
1427
|
+
required=True,
|
|
1428
|
+
help="Name of session to clear",
|
|
1429
|
+
)
|
|
1430
|
+
def logout(session_name: str) -> None:
|
|
1431
|
+
"""
|
|
1432
|
+
Clear a cached AWS session.
|
|
1433
|
+
|
|
1434
|
+
This removes the cached session tokens for the specified session name.
|
|
1435
|
+
|
|
1436
|
+
Example:
|
|
1437
|
+
regscale aws auth logout --session-name my-session
|
|
1438
|
+
"""
|
|
1439
|
+
try:
|
|
1440
|
+
from .session_manager import AWSSessionManager
|
|
1441
|
+
|
|
1442
|
+
manager = AWSSessionManager()
|
|
481
1443
|
|
|
482
|
-
if
|
|
483
|
-
|
|
484
|
-
click.echo(json.dumps(findings, indent=2, cls=DateTimeEncoder))
|
|
1444
|
+
if manager.clear_session(session_name):
|
|
1445
|
+
click.echo(click.style(f"✓ Session '{session_name}' cleared successfully!", fg="green"))
|
|
485
1446
|
else:
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
1447
|
+
click.echo(click.style(f"Session '{session_name}' not found.", fg="yellow"))
|
|
1448
|
+
|
|
1449
|
+
except Exception as e:
|
|
1450
|
+
logger.error(f"Failed to clear session: {e}", exc_info=True)
|
|
1451
|
+
raise click.ClickException(str(e))
|
|
1452
|
+
|
|
1453
|
+
|
|
1454
|
+
@auth.command(name="logout-all")
|
|
1455
|
+
@click.confirmation_option(prompt="Are you sure you want to clear all cached sessions?")
|
|
1456
|
+
def logout_all() -> None:
|
|
1457
|
+
"""
|
|
1458
|
+
Clear all cached AWS sessions.
|
|
489
1459
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
1460
|
+
This removes all cached session tokens.
|
|
1461
|
+
|
|
1462
|
+
Example:
|
|
1463
|
+
regscale aws auth logout-all
|
|
1464
|
+
"""
|
|
1465
|
+
try:
|
|
1466
|
+
from .session_manager import AWSSessionManager
|
|
1467
|
+
|
|
1468
|
+
manager = AWSSessionManager()
|
|
1469
|
+
count = manager.clear_all_sessions()
|
|
1470
|
+
|
|
1471
|
+
click.echo(click.style(f"✓ Cleared {count} session(s) successfully!", fg="green"))
|
|
493
1472
|
|
|
494
1473
|
except Exception as e:
|
|
495
|
-
logger.error(f"
|
|
1474
|
+
logger.error(f"Failed to clear sessions: {e}", exc_info=True)
|
|
1475
|
+
raise click.ClickException(str(e))
|
|
1476
|
+
|
|
1477
|
+
|
|
1478
|
+
@auth.command(name="list")
|
|
1479
|
+
def list_sessions() -> None:
|
|
1480
|
+
"""
|
|
1481
|
+
List all cached AWS sessions.
|
|
1482
|
+
|
|
1483
|
+
This shows all cached session tokens with their expiration status.
|
|
1484
|
+
|
|
1485
|
+
Example:
|
|
1486
|
+
regscale aws auth list
|
|
1487
|
+
"""
|
|
1488
|
+
try:
|
|
1489
|
+
from .session_manager import AWSSessionManager
|
|
1490
|
+
|
|
1491
|
+
manager = AWSSessionManager()
|
|
1492
|
+
sessions = manager.list_sessions()
|
|
1493
|
+
|
|
1494
|
+
if not sessions:
|
|
1495
|
+
click.echo("No cached sessions found.")
|
|
1496
|
+
return
|
|
1497
|
+
|
|
1498
|
+
click.echo("\nCached AWS Sessions:")
|
|
1499
|
+
click.echo("=" * 80)
|
|
1500
|
+
|
|
1501
|
+
for session in sessions:
|
|
1502
|
+
status = click.style("EXPIRED", fg="red") if session["expired"] else click.style("ACTIVE", fg="green")
|
|
1503
|
+
click.echo(f"\nSession: {click.style(session['name'], bold=True)}")
|
|
1504
|
+
click.echo(f" Status: {status}")
|
|
1505
|
+
click.echo(f" Region: {session['region']}")
|
|
1506
|
+
click.echo(f" Expires: {session['expiration']}")
|
|
1507
|
+
click.echo(f" Cached At: {session['cached_at']}")
|
|
1508
|
+
|
|
1509
|
+
click.echo("\n" + "=" * 80)
|
|
1510
|
+
|
|
1511
|
+
except Exception as e:
|
|
1512
|
+
logger.error(f"Failed to list sessions: {e}", exc_info=True)
|
|
1513
|
+
raise click.ClickException(str(e))
|
|
1514
|
+
|
|
1515
|
+
|
|
1516
|
+
@awsv2.command(name="sync_compliance")
|
|
1517
|
+
@click.option(
|
|
1518
|
+
"--region",
|
|
1519
|
+
type=str,
|
|
1520
|
+
default=os.environ.get("AWS_REGION", "us-east-1"),
|
|
1521
|
+
help="AWS region to collect compliance data from",
|
|
1522
|
+
)
|
|
1523
|
+
@click.option(
|
|
1524
|
+
"--regscale_id",
|
|
1525
|
+
"--id",
|
|
1526
|
+
type=click.INT,
|
|
1527
|
+
help="RegScale will create and update compliance assessments as children of this record.",
|
|
1528
|
+
required=True,
|
|
1529
|
+
)
|
|
1530
|
+
@click.option(
|
|
1531
|
+
"--session-name",
|
|
1532
|
+
type=str,
|
|
1533
|
+
required=False,
|
|
1534
|
+
help="Name of cached AWS session to use (from 'regscale aws auth login')",
|
|
1535
|
+
)
|
|
1536
|
+
@click.option(
|
|
1537
|
+
"--profile",
|
|
1538
|
+
type=str,
|
|
1539
|
+
required=False,
|
|
1540
|
+
help="AWS profile name from ~/.aws/credentials",
|
|
1541
|
+
envvar="AWS_PROFILE",
|
|
1542
|
+
)
|
|
1543
|
+
@click.option(
|
|
1544
|
+
"--aws_access_key_id",
|
|
1545
|
+
type=str,
|
|
1546
|
+
required=False,
|
|
1547
|
+
help="AWS access key ID (overrides profile)",
|
|
1548
|
+
envvar="AWS_ACCESS_KEY_ID",
|
|
1549
|
+
)
|
|
1550
|
+
@click.option(
|
|
1551
|
+
"--aws_secret_access_key",
|
|
1552
|
+
type=str,
|
|
1553
|
+
required=False,
|
|
1554
|
+
help="AWS secret access key (overrides profile)",
|
|
1555
|
+
default=os.getenv("AWS_SECRET_ACCESS_KEY"),
|
|
1556
|
+
)
|
|
1557
|
+
@click.option(
|
|
1558
|
+
"--aws_session_token",
|
|
1559
|
+
type=click.STRING,
|
|
1560
|
+
required=False,
|
|
1561
|
+
help="AWS session token (overrides profile)",
|
|
1562
|
+
default=os.environ.get("AWS_SESSION_TOKEN"),
|
|
1563
|
+
)
|
|
1564
|
+
@click.option(
|
|
1565
|
+
"--account-id",
|
|
1566
|
+
type=str,
|
|
1567
|
+
required=False,
|
|
1568
|
+
help="Filter resources by AWS account ID (from ARN)",
|
|
1569
|
+
)
|
|
1570
|
+
@click.option(
|
|
1571
|
+
"--tags",
|
|
1572
|
+
type=str,
|
|
1573
|
+
required=False,
|
|
1574
|
+
help="Filter resources by tags (format: key1=value1,key2=value2). All specified tags must match (AND logic).",
|
|
1575
|
+
)
|
|
1576
|
+
@click.option(
|
|
1577
|
+
"--framework",
|
|
1578
|
+
type=click.Choice(
|
|
1579
|
+
["NIST800-53R5", "SOC2", "PCI DSS", "HIPAA", "GDPR", "ISO27001", "CIS", "CUSTOM"],
|
|
1580
|
+
case_sensitive=False,
|
|
1581
|
+
),
|
|
1582
|
+
default="NIST800-53R5",
|
|
1583
|
+
help="Compliance framework to sync. Only assessments matching this framework will be processed. "
|
|
1584
|
+
"Use CUSTOM for custom frameworks and provide --custom-framework-name. "
|
|
1585
|
+
"NOTE: Framework filtering is bypassed when --assessment-id is specified.",
|
|
1586
|
+
)
|
|
1587
|
+
@click.option(
|
|
1588
|
+
"--custom-framework-name",
|
|
1589
|
+
type=str,
|
|
1590
|
+
required=False,
|
|
1591
|
+
help="Custom framework name for CUSTOM framework types. Required when --framework=CUSTOM.",
|
|
1592
|
+
)
|
|
1593
|
+
@click.option(
|
|
1594
|
+
"--assessment-id",
|
|
1595
|
+
type=str,
|
|
1596
|
+
required=False,
|
|
1597
|
+
help="Specific AWS Audit Manager assessment ID to sync. When provided, framework filtering is bypassed "
|
|
1598
|
+
"and the specified assessment is used directly regardless of its framework type.",
|
|
1599
|
+
)
|
|
1600
|
+
@click.option(
|
|
1601
|
+
"--create-issues/--no-create-issues",
|
|
1602
|
+
default=True,
|
|
1603
|
+
help="Create issues for failed compliance controls (default: True)",
|
|
1604
|
+
)
|
|
1605
|
+
@click.option(
|
|
1606
|
+
"--update-control-status/--no-update-control-status",
|
|
1607
|
+
default=True,
|
|
1608
|
+
help="Update control implementation status based on compliance results (default: True)",
|
|
1609
|
+
)
|
|
1610
|
+
@click.option(
|
|
1611
|
+
"--create-poams",
|
|
1612
|
+
"-cp",
|
|
1613
|
+
is_flag=True,
|
|
1614
|
+
default=False,
|
|
1615
|
+
help="Mark created issues as POAMs (default: False)",
|
|
1616
|
+
)
|
|
1617
|
+
@click.option(
|
|
1618
|
+
"--collect-evidence/--no-collect-evidence",
|
|
1619
|
+
default=False,
|
|
1620
|
+
help="Collect and store evidence artifacts from AWS Audit Manager (default: False)",
|
|
1621
|
+
)
|
|
1622
|
+
@click.option(
|
|
1623
|
+
"--evidence-control-ids",
|
|
1624
|
+
type=str,
|
|
1625
|
+
required=False,
|
|
1626
|
+
help="Comma-separated list of control IDs to collect evidence for (e.g., 'AU-2,AU-3,AU-6'). "
|
|
1627
|
+
"If not specified, evidence is collected for all controls in the assessment.",
|
|
1628
|
+
)
|
|
1629
|
+
@click.option(
|
|
1630
|
+
"--evidence-frequency",
|
|
1631
|
+
type=int,
|
|
1632
|
+
default=30,
|
|
1633
|
+
help="Evidence update frequency in days (default: 30)",
|
|
1634
|
+
)
|
|
1635
|
+
@click.option(
|
|
1636
|
+
"--max-evidence-per-control",
|
|
1637
|
+
type=int,
|
|
1638
|
+
default=100,
|
|
1639
|
+
help="Maximum number of evidence items to collect per control (default: 100, max: 1000)",
|
|
1640
|
+
)
|
|
1641
|
+
@click.option(
|
|
1642
|
+
"--use-assessment-evidence-folders/--no-use-assessment-evidence-folders",
|
|
1643
|
+
default=True,
|
|
1644
|
+
help="Use assessment-level evidence collection (faster, automatic) vs control-level (slower, requires manual report). "
|
|
1645
|
+
"Assessment-level collects evidence from assessment folders directly. Control-level requires assessment report generation. "
|
|
1646
|
+
"(default: True - use assessment folders)",
|
|
1647
|
+
)
|
|
1648
|
+
@click.option(
|
|
1649
|
+
"--force-refresh",
|
|
1650
|
+
is_flag=True,
|
|
1651
|
+
default=False,
|
|
1652
|
+
help="Force refresh of compliance data by bypassing cache (default: False)",
|
|
1653
|
+
)
|
|
1654
|
+
@click.option(
|
|
1655
|
+
"--use-enhanced-analyzer/--no-enhanced-analyzer",
|
|
1656
|
+
default=True,
|
|
1657
|
+
help="Use enhanced ControlComplianceAnalyzer for evidence-based compliance determination (default: True)",
|
|
1658
|
+
)
|
|
1659
|
+
@click.pass_context
|
|
1660
|
+
def sync_compliance(ctx, **kwargs) -> None:
|
|
1661
|
+
"""
|
|
1662
|
+
Sync AWS Audit Manager compliance assessments to RegScale.
|
|
1663
|
+
|
|
1664
|
+
This command fetches compliance assessment results from AWS Audit Manager and:
|
|
1665
|
+
- Creates control assessments in RegScale based on AWS assessment results
|
|
1666
|
+
- Creates issues for failed compliance controls (optional)
|
|
1667
|
+
- Updates control implementation status (optional)
|
|
1668
|
+
- Collects and stores evidence artifacts from AWS Audit Manager (optional)
|
|
1669
|
+
- Supports multiple compliance frameworks (NIST 800-53, SOC2, PCI DSS, etc.)
|
|
1670
|
+
|
|
1671
|
+
AWS Audit Manager provides automated evidence collection and compliance
|
|
1672
|
+
assessments against standard and custom frameworks. This integration brings
|
|
1673
|
+
those assessments into RegScale for unified compliance management.
|
|
1674
|
+
|
|
1675
|
+
Filtering support:
|
|
1676
|
+
- Account ID: Filter resources by AWS account ID (extracted from ARNs)
|
|
1677
|
+
- Tags: Filter resources by tag key-value pairs (AND logic - all must match)
|
|
1678
|
+
|
|
1679
|
+
Evidence Collection:
|
|
1680
|
+
Use --collect-evidence to retrieve and store evidence artifacts from AWS Audit Manager.
|
|
1681
|
+
Evidence is collected from evidence folders for each control and stored as JSONL
|
|
1682
|
+
attachments in RegScale Evidence records. Evidence includes CloudTrail events,
|
|
1683
|
+
AWS Config snapshots, and other automated evidence sources.
|
|
1684
|
+
|
|
1685
|
+
Authentication methods (in priority order):
|
|
1686
|
+
1. Cached session: --session-name (from 'regscale aws auth login')
|
|
1687
|
+
2. Explicit credentials: --aws_access_key_id + --aws_secret_access_key
|
|
1688
|
+
3. AWS profile: --profile
|
|
1689
|
+
4. Environment variables or default AWS credential chain
|
|
1690
|
+
|
|
1691
|
+
Examples:
|
|
1692
|
+
# Sync all assessments for a plan
|
|
1693
|
+
regscale aws sync_compliance --regscale_id 123
|
|
1694
|
+
|
|
1695
|
+
# Sync specific assessment (bypasses framework filtering)
|
|
1696
|
+
regscale aws sync_compliance --regscale_id 123 --assessment-id abc-123
|
|
1697
|
+
|
|
1698
|
+
# Sync custom framework assessment by ID
|
|
1699
|
+
regscale aws sync_compliance --regscale_id 123 --assessment-id abc-123 \\
|
|
1700
|
+
--framework CUSTOM --custom-framework-name "DOC Moderate Baseline"
|
|
1701
|
+
# NOTE: When --assessment-id is provided, the framework parameters are optional
|
|
1702
|
+
|
|
1703
|
+
# Sync without creating issues
|
|
1704
|
+
regscale aws sync_compliance --regscale_id 123 --no-create-issues
|
|
1705
|
+
|
|
1706
|
+
# Sync with evidence collection
|
|
1707
|
+
regscale aws sync_compliance --regscale_id 123 --collect-evidence
|
|
1708
|
+
|
|
1709
|
+
# Collect evidence only for specific controls
|
|
1710
|
+
regscale aws sync_compliance --regscale_id 123 --collect-evidence \\
|
|
1711
|
+
--evidence-control-ids AU-2,AU-3,AU-6,AC-2
|
|
1712
|
+
"""
|
|
1713
|
+
try:
|
|
1714
|
+
# Extract parameters from kwargs
|
|
1715
|
+
region = kwargs.get("region", "us-east-1")
|
|
1716
|
+
regscale_id = kwargs.get("regscale_id")
|
|
1717
|
+
session_name = kwargs.get("session_name")
|
|
1718
|
+
profile = kwargs.get("profile")
|
|
1719
|
+
aws_access_key_id = kwargs.get("aws_access_key_id")
|
|
1720
|
+
aws_secret_access_key = kwargs.get("aws_secret_access_key")
|
|
1721
|
+
aws_session_token = kwargs.get("aws_session_token")
|
|
1722
|
+
account_id = kwargs.get("account_id")
|
|
1723
|
+
tags = kwargs.get("tags")
|
|
1724
|
+
framework = kwargs.get("framework", "NIST800-53R5")
|
|
1725
|
+
custom_framework_name = kwargs.get("custom_framework_name")
|
|
1726
|
+
assessment_id = kwargs.get("assessment_id")
|
|
1727
|
+
create_issues = kwargs.get("create_issues", True)
|
|
1728
|
+
update_control_status = kwargs.get("update_control_status", True)
|
|
1729
|
+
create_poams = kwargs.get("create_poams", False)
|
|
1730
|
+
collect_evidence = kwargs.get("collect_evidence", False)
|
|
1731
|
+
evidence_control_ids = kwargs.get("evidence_control_ids")
|
|
1732
|
+
evidence_frequency = kwargs.get("evidence_frequency", 30)
|
|
1733
|
+
max_evidence_per_control = kwargs.get("max_evidence_per_control", 100)
|
|
1734
|
+
use_assessment_evidence_folders = kwargs.get("use_assessment_evidence_folders", True)
|
|
1735
|
+
force_refresh = kwargs.get("force_refresh", False)
|
|
1736
|
+
use_enhanced_analyzer = kwargs.get("use_enhanced_analyzer", True)
|
|
1737
|
+
|
|
1738
|
+
# Parse evidence control IDs into list
|
|
1739
|
+
evidence_control_list = None
|
|
1740
|
+
if evidence_control_ids:
|
|
1741
|
+
evidence_control_list = [cid.strip() for cid in evidence_control_ids.split(",")]
|
|
1742
|
+
|
|
1743
|
+
# Build configuration objects
|
|
1744
|
+
sync_config = ComplianceSyncConfig(
|
|
1745
|
+
region=region,
|
|
1746
|
+
regscale_id=regscale_id,
|
|
1747
|
+
framework=framework,
|
|
1748
|
+
custom_framework_name=custom_framework_name,
|
|
1749
|
+
assessment_id=assessment_id,
|
|
1750
|
+
create_issues=create_issues,
|
|
1751
|
+
update_control_status=update_control_status,
|
|
1752
|
+
create_poams=create_poams,
|
|
1753
|
+
collect_evidence=collect_evidence,
|
|
1754
|
+
evidence_control_ids=evidence_control_list,
|
|
1755
|
+
evidence_frequency=evidence_frequency,
|
|
1756
|
+
max_evidence_per_control=max_evidence_per_control,
|
|
1757
|
+
use_assessment_evidence_folders=use_assessment_evidence_folders,
|
|
1758
|
+
force_refresh=force_refresh,
|
|
1759
|
+
use_enhanced_analyzer=use_enhanced_analyzer,
|
|
1760
|
+
)
|
|
1761
|
+
|
|
1762
|
+
credential_config = AWSCredentialConfig(
|
|
1763
|
+
session_name=session_name,
|
|
1764
|
+
profile=profile,
|
|
1765
|
+
aws_access_key_id=aws_access_key_id,
|
|
1766
|
+
aws_secret_access_key=aws_secret_access_key,
|
|
1767
|
+
aws_session_token=aws_session_token,
|
|
1768
|
+
account_id=account_id,
|
|
1769
|
+
tags=parse_tags(tags),
|
|
1770
|
+
)
|
|
1771
|
+
|
|
1772
|
+
# Delegate to helper function
|
|
1773
|
+
_execute_compliance_sync(sync_config, credential_config)
|
|
1774
|
+
|
|
1775
|
+
except Exception as e:
|
|
1776
|
+
logger.error(f"Error syncing AWS Audit Manager compliance: {e}", exc_info=True)
|
|
1777
|
+
raise click.ClickException(str(e))
|
|
1778
|
+
|
|
1779
|
+
|
|
1780
|
+
def _execute_compliance_sync(sync_config: ComplianceSyncConfig, credential_config: AWSCredentialConfig) -> None:
|
|
1781
|
+
"""
|
|
1782
|
+
Execute the compliance sync with provided configurations.
|
|
1783
|
+
|
|
1784
|
+
:param ComplianceSyncConfig sync_config: Sync configuration
|
|
1785
|
+
:param AWSCredentialConfig credential_config: AWS credential configuration
|
|
1786
|
+
:return: None
|
|
1787
|
+
:rtype: None
|
|
1788
|
+
"""
|
|
1789
|
+
from .audit_manager_compliance import AWSAuditManagerCompliance
|
|
1790
|
+
|
|
1791
|
+
logger.info("Starting AWS Audit Manager compliance sync to RegScale...")
|
|
1792
|
+
|
|
1793
|
+
# Log filtering information
|
|
1794
|
+
if credential_config.account_id:
|
|
1795
|
+
logger.info(f"Filtering resources by account ID: {credential_config.account_id}")
|
|
1796
|
+
if credential_config.tags:
|
|
1797
|
+
logger.info(f"Filtering resources by tags: {credential_config.tags}")
|
|
1798
|
+
|
|
1799
|
+
# Resolve AWS credentials
|
|
1800
|
+
profile, access_key, secret_key, session_token, region = resolve_aws_credentials(
|
|
1801
|
+
credential_config.session_name,
|
|
1802
|
+
credential_config.profile,
|
|
1803
|
+
credential_config.aws_access_key_id,
|
|
1804
|
+
credential_config.aws_secret_access_key,
|
|
1805
|
+
credential_config.aws_session_token,
|
|
1806
|
+
sync_config.region,
|
|
1807
|
+
)
|
|
1808
|
+
|
|
1809
|
+
# Log credential resolution results
|
|
1810
|
+
logger.info(
|
|
1811
|
+
f"Using AWS credentials - profile: {profile if profile else 'not set'}, "
|
|
1812
|
+
f"explicit credentials: {'yes' if access_key else 'no'}, region: {region}"
|
|
1813
|
+
)
|
|
1814
|
+
|
|
1815
|
+
# Log evidence collection request
|
|
1816
|
+
if sync_config.evidence_control_ids:
|
|
1817
|
+
logger.info(f"Evidence collection requested for controls: {sync_config.evidence_control_ids}")
|
|
1818
|
+
|
|
1819
|
+
# Create scanner and execute sync
|
|
1820
|
+
scanner = AWSAuditManagerCompliance(
|
|
1821
|
+
plan_id=sync_config.regscale_id,
|
|
1822
|
+
region=region,
|
|
1823
|
+
profile=profile,
|
|
1824
|
+
aws_access_key_id=access_key,
|
|
1825
|
+
aws_secret_access_key=secret_key,
|
|
1826
|
+
aws_session_token=session_token,
|
|
1827
|
+
framework=sync_config.framework,
|
|
1828
|
+
custom_framework_name=sync_config.custom_framework_name,
|
|
1829
|
+
assessment_id=sync_config.assessment_id,
|
|
1830
|
+
create_issues=sync_config.create_issues,
|
|
1831
|
+
update_control_status=sync_config.update_control_status,
|
|
1832
|
+
create_poams=sync_config.create_poams,
|
|
1833
|
+
collect_evidence=sync_config.collect_evidence,
|
|
1834
|
+
evidence_control_ids=sync_config.evidence_control_ids,
|
|
1835
|
+
evidence_frequency=sync_config.evidence_frequency,
|
|
1836
|
+
max_evidence_per_control=sync_config.max_evidence_per_control,
|
|
1837
|
+
use_assessment_evidence_folders=sync_config.use_assessment_evidence_folders,
|
|
1838
|
+
account_id=credential_config.account_id,
|
|
1839
|
+
tags=credential_config.tags,
|
|
1840
|
+
force_refresh=sync_config.force_refresh,
|
|
1841
|
+
use_enhanced_analyzer=sync_config.use_enhanced_analyzer,
|
|
1842
|
+
)
|
|
1843
|
+
|
|
1844
|
+
scanner.sync_compliance()
|
|
1845
|
+
|
|
1846
|
+
logger.info("AWS Audit Manager compliance sync completed successfully")
|
|
1847
|
+
|
|
1848
|
+
|
|
1849
|
+
@awsv2.command(name="sync_config_compliance")
|
|
1850
|
+
@click.option(
|
|
1851
|
+
"--region",
|
|
1852
|
+
type=str,
|
|
1853
|
+
default=os.environ.get("AWS_REGION", "us-east-1"),
|
|
1854
|
+
help="AWS region to collect compliance data from",
|
|
1855
|
+
)
|
|
1856
|
+
@click.option(
|
|
1857
|
+
"--regscale_id",
|
|
1858
|
+
"--id",
|
|
1859
|
+
type=click.INT,
|
|
1860
|
+
help="RegScale will create and update compliance assessments as children of this record.",
|
|
1861
|
+
required=True,
|
|
1862
|
+
)
|
|
1863
|
+
@click.option(
|
|
1864
|
+
"--session-name",
|
|
1865
|
+
type=str,
|
|
1866
|
+
required=False,
|
|
1867
|
+
help="Name of cached AWS session to use (from 'regscale aws auth login')",
|
|
1868
|
+
)
|
|
1869
|
+
@click.option(
|
|
1870
|
+
"--profile",
|
|
1871
|
+
type=str,
|
|
1872
|
+
required=False,
|
|
1873
|
+
help="AWS profile name from ~/.aws/credentials",
|
|
1874
|
+
envvar="AWS_PROFILE",
|
|
1875
|
+
)
|
|
1876
|
+
@click.option(
|
|
1877
|
+
"--aws_access_key_id",
|
|
1878
|
+
type=str,
|
|
1879
|
+
required=False,
|
|
1880
|
+
help="AWS access key ID (overrides profile)",
|
|
1881
|
+
envvar="AWS_ACCESS_KEY_ID",
|
|
1882
|
+
)
|
|
1883
|
+
@click.option(
|
|
1884
|
+
"--aws_secret_access_key",
|
|
1885
|
+
type=str,
|
|
1886
|
+
required=False,
|
|
1887
|
+
help="AWS secret access key (overrides profile)",
|
|
1888
|
+
default=os.getenv("AWS_SECRET_ACCESS_KEY"),
|
|
1889
|
+
)
|
|
1890
|
+
@click.option(
|
|
1891
|
+
"--aws_session_token",
|
|
1892
|
+
type=click.STRING,
|
|
1893
|
+
required=False,
|
|
1894
|
+
help="AWS session token (overrides profile)",
|
|
1895
|
+
default=os.environ.get("AWS_SESSION_TOKEN"),
|
|
1896
|
+
)
|
|
1897
|
+
@click.option(
|
|
1898
|
+
"--account-id",
|
|
1899
|
+
type=str,
|
|
1900
|
+
required=False,
|
|
1901
|
+
help="Filter Config rules by AWS account ID (from ARN)",
|
|
1902
|
+
)
|
|
1903
|
+
@click.option(
|
|
1904
|
+
"--tags",
|
|
1905
|
+
type=str,
|
|
1906
|
+
required=False,
|
|
1907
|
+
help="Filter Config rules by tags (format: key1=value1,key2=value2). All specified tags must match (AND logic).",
|
|
1908
|
+
)
|
|
1909
|
+
@click.option(
|
|
1910
|
+
"--framework",
|
|
1911
|
+
type=click.Choice(
|
|
1912
|
+
["NIST800-53R5", "SOC2", "PCI DSS", "HIPAA", "GDPR", "ISO27001", "CIS"],
|
|
1913
|
+
case_sensitive=False,
|
|
1914
|
+
),
|
|
1915
|
+
default="NIST800-53R5",
|
|
1916
|
+
help="Compliance framework to sync. Only rules matching this framework will be processed.",
|
|
1917
|
+
)
|
|
1918
|
+
@click.option(
|
|
1919
|
+
"--conformance-pack-name",
|
|
1920
|
+
type=str,
|
|
1921
|
+
required=False,
|
|
1922
|
+
help="Specific AWS Config conformance pack to sync (optional)",
|
|
1923
|
+
)
|
|
1924
|
+
@click.option(
|
|
1925
|
+
"--create-issues/--no-create-issues",
|
|
1926
|
+
default=True,
|
|
1927
|
+
help="Create issues for failed compliance controls (default: True)",
|
|
1928
|
+
)
|
|
1929
|
+
@click.option(
|
|
1930
|
+
"--update-control-status/--no-update-control-status",
|
|
1931
|
+
default=True,
|
|
1932
|
+
help="Update control implementation status based on compliance results (default: True)",
|
|
1933
|
+
)
|
|
1934
|
+
@click.option(
|
|
1935
|
+
"--create-poams",
|
|
1936
|
+
"-cp",
|
|
1937
|
+
is_flag=True,
|
|
1938
|
+
default=False,
|
|
1939
|
+
help="Mark created issues as POAMs (default: False)",
|
|
1940
|
+
)
|
|
1941
|
+
@click.option(
|
|
1942
|
+
"--collect-evidence/--no-collect-evidence",
|
|
1943
|
+
default=False,
|
|
1944
|
+
help="Collect and store evidence artifacts from AWS Config (default: False)",
|
|
1945
|
+
)
|
|
1946
|
+
@click.option(
|
|
1947
|
+
"--evidence-as-attachments/--no-evidence-as-attachments",
|
|
1948
|
+
default=True,
|
|
1949
|
+
help="Store evidence as SSP-level file attachments (default: True)",
|
|
1950
|
+
)
|
|
1951
|
+
@click.option(
|
|
1952
|
+
"--evidence-as-records",
|
|
1953
|
+
is_flag=True,
|
|
1954
|
+
default=False,
|
|
1955
|
+
help="Create individual Evidence records per control (like Audit Manager)",
|
|
1956
|
+
)
|
|
1957
|
+
@click.option(
|
|
1958
|
+
"--evidence-control-ids",
|
|
1959
|
+
type=str,
|
|
1960
|
+
required=False,
|
|
1961
|
+
help="Comma-separated list of control IDs to collect evidence for (e.g., 'AU-2,AU-3,AU-6'). "
|
|
1962
|
+
"If not specified, evidence is collected for all controls.",
|
|
1963
|
+
)
|
|
1964
|
+
@click.option(
|
|
1965
|
+
"--evidence-frequency",
|
|
1966
|
+
type=int,
|
|
1967
|
+
default=30,
|
|
1968
|
+
help="Evidence update frequency in days (default: 30)",
|
|
1969
|
+
)
|
|
1970
|
+
@click.option(
|
|
1971
|
+
"--use-security-hub/--no-use-security-hub",
|
|
1972
|
+
default=False,
|
|
1973
|
+
help="Include AWS Security Hub control findings (default: False)",
|
|
1974
|
+
)
|
|
1975
|
+
@click.option(
|
|
1976
|
+
"--force-refresh",
|
|
1977
|
+
is_flag=True,
|
|
1978
|
+
default=False,
|
|
1979
|
+
help="Force refresh of compliance data by bypassing cache (default: False)",
|
|
1980
|
+
)
|
|
1981
|
+
@click.pass_context
|
|
1982
|
+
def sync_config_compliance(ctx, **kwargs) -> None:
|
|
1983
|
+
"""
|
|
1984
|
+
Sync AWS Config compliance assessments to RegScale (alternative to Audit Manager).
|
|
1985
|
+
|
|
1986
|
+
This command provides equivalent functionality to AWS Audit Manager using AWS Config
|
|
1987
|
+
and optionally Security Hub. Use this when Audit Manager is not available.
|
|
1988
|
+
|
|
1989
|
+
This command fetches compliance assessment results from AWS Config rules and:
|
|
1990
|
+
- Creates control assessments in RegScale based on Config rule evaluation results
|
|
1991
|
+
- Creates issues for failed compliance controls (optional)
|
|
1992
|
+
- Updates control implementation status (optional)
|
|
1993
|
+
- Collects evidence artifacts from Config evaluations (optional)
|
|
1994
|
+
|
|
1995
|
+
Evidence Collection Modes:
|
|
1996
|
+
- Default (--evidence-as-attachments): Creates consolidated evidence file attached to SSP
|
|
1997
|
+
- Optional (--evidence-as-records): Creates individual Evidence records per control
|
|
1998
|
+
|
|
1999
|
+
Examples:
|
|
2000
|
+
# Basic compliance sync (no evidence)
|
|
2001
|
+
regscale aws sync_config_compliance --regscale_id 123 --framework NIST800-53R5
|
|
2002
|
+
|
|
2003
|
+
# Filter by account ID
|
|
2004
|
+
regscale aws sync_config_compliance --regscale_id 123 --account-id 123456789012
|
|
2005
|
+
|
|
2006
|
+
# Filter by tags
|
|
2007
|
+
regscale aws sync_config_compliance --regscale_id 123 --tags Environment=Production,Owner=Security
|
|
2008
|
+
|
|
2009
|
+
# Combine account and tag filtering
|
|
2010
|
+
regscale aws sync_config_compliance --regscale_id 123 --account-id 123456789012 \\
|
|
2011
|
+
--tags Environment=Production
|
|
2012
|
+
|
|
2013
|
+
# With evidence as SSP attachments (default)
|
|
2014
|
+
regscale aws sync_config_compliance --regscale_id 123 --collect-evidence
|
|
2015
|
+
|
|
2016
|
+
# With evidence as individual records (Audit Manager style)
|
|
2017
|
+
regscale aws sync_config_compliance --regscale_id 123 --collect-evidence --evidence-as-records
|
|
2018
|
+
|
|
2019
|
+
# Evidence for specific controls only
|
|
2020
|
+
regscale aws sync_config_compliance --regscale_id 123 --collect-evidence \\
|
|
2021
|
+
--evidence-control-ids AC-2,AU-3,SI-2
|
|
2022
|
+
|
|
2023
|
+
# With Security Hub integration
|
|
2024
|
+
regscale aws sync_config_compliance --regscale_id 123 --use-security-hub
|
|
2025
|
+
"""
|
|
2026
|
+
try:
|
|
2027
|
+
# Extract parameters from kwargs
|
|
2028
|
+
region = kwargs["region"]
|
|
2029
|
+
regscale_id = kwargs["regscale_id"]
|
|
2030
|
+
framework = kwargs["framework"]
|
|
2031
|
+
session_name = kwargs.get("session_name")
|
|
2032
|
+
profile = kwargs.get("profile")
|
|
2033
|
+
aws_access_key_id = kwargs.get("aws_access_key_id")
|
|
2034
|
+
aws_secret_access_key = kwargs.get("aws_secret_access_key")
|
|
2035
|
+
aws_session_token = kwargs.get("aws_session_token")
|
|
2036
|
+
account_id = kwargs.get("account_id")
|
|
2037
|
+
tags = kwargs.get("tags")
|
|
2038
|
+
conformance_pack_name = kwargs.get("conformance_pack_name")
|
|
2039
|
+
create_issues = kwargs.get("create_issues", True)
|
|
2040
|
+
update_control_status = kwargs.get("update_control_status", True)
|
|
2041
|
+
create_poams = kwargs.get("create_poams", False)
|
|
2042
|
+
collect_evidence = kwargs.get("collect_evidence", False)
|
|
2043
|
+
evidence_as_attachments = kwargs.get("evidence_as_attachments", True)
|
|
2044
|
+
evidence_as_records = kwargs.get("evidence_as_records", False)
|
|
2045
|
+
evidence_control_ids = kwargs.get("evidence_control_ids")
|
|
2046
|
+
evidence_frequency = kwargs.get("evidence_frequency", 30)
|
|
2047
|
+
use_security_hub = kwargs.get("use_security_hub", False)
|
|
2048
|
+
force_refresh = kwargs.get("force_refresh", False)
|
|
2049
|
+
|
|
2050
|
+
# Parse evidence control IDs
|
|
2051
|
+
evidence_control_list = None
|
|
2052
|
+
if evidence_control_ids:
|
|
2053
|
+
evidence_control_list = [cid.strip() for cid in evidence_control_ids.split(",")]
|
|
2054
|
+
|
|
2055
|
+
# Parse tags
|
|
2056
|
+
parsed_tags = parse_tags(tags) if tags else None
|
|
2057
|
+
|
|
2058
|
+
# Resolve AWS credentials
|
|
2059
|
+
resolved_profile, access_key, secret_key, session_token, resolved_region = resolve_aws_credentials(
|
|
2060
|
+
session_name,
|
|
2061
|
+
profile,
|
|
2062
|
+
aws_access_key_id,
|
|
2063
|
+
aws_secret_access_key,
|
|
2064
|
+
aws_session_token,
|
|
2065
|
+
region,
|
|
2066
|
+
)
|
|
2067
|
+
|
|
2068
|
+
# Log credential resolution results
|
|
2069
|
+
logger.info(
|
|
2070
|
+
f"Using AWS credentials - profile: {resolved_profile if resolved_profile else 'not set'}, "
|
|
2071
|
+
f"explicit credentials: {'yes' if access_key else 'no'}, region: {resolved_region}"
|
|
2072
|
+
)
|
|
2073
|
+
|
|
2074
|
+
# Log filtering information
|
|
2075
|
+
if account_id:
|
|
2076
|
+
logger.info(f"Filtering Config rules by account ID: {account_id}")
|
|
2077
|
+
if parsed_tags:
|
|
2078
|
+
logger.info(f"Filtering Config rules by tags: {parsed_tags}")
|
|
2079
|
+
|
|
2080
|
+
# Log evidence collection request
|
|
2081
|
+
if collect_evidence:
|
|
2082
|
+
if evidence_as_records:
|
|
2083
|
+
logger.info(EVIDENCE_MODE_INDIVIDUAL_RECORDS)
|
|
2084
|
+
else:
|
|
2085
|
+
logger.info(EVIDENCE_MODE_SSP_ATTACHMENTS)
|
|
2086
|
+
|
|
2087
|
+
if evidence_control_list:
|
|
2088
|
+
logger.info(f"Evidence collection requested for controls: {evidence_control_list}")
|
|
2089
|
+
|
|
2090
|
+
# Import and create scanner
|
|
2091
|
+
from .config_compliance import AWSConfigCompliance
|
|
2092
|
+
|
|
2093
|
+
logger.info("Starting AWS Config compliance sync to RegScale...")
|
|
2094
|
+
|
|
2095
|
+
scanner = AWSConfigCompliance(
|
|
2096
|
+
plan_id=regscale_id,
|
|
2097
|
+
region=resolved_region,
|
|
2098
|
+
profile=resolved_profile,
|
|
2099
|
+
aws_access_key_id=access_key,
|
|
2100
|
+
aws_secret_access_key=secret_key,
|
|
2101
|
+
aws_session_token=session_token,
|
|
2102
|
+
account_id=account_id,
|
|
2103
|
+
tags=parsed_tags,
|
|
2104
|
+
framework=framework,
|
|
2105
|
+
conformance_pack_name=conformance_pack_name,
|
|
2106
|
+
create_issues=create_issues,
|
|
2107
|
+
update_control_status=update_control_status,
|
|
2108
|
+
create_poams=create_poams,
|
|
2109
|
+
collect_evidence=collect_evidence,
|
|
2110
|
+
evidence_as_attachments=evidence_as_attachments,
|
|
2111
|
+
evidence_as_records=evidence_as_records,
|
|
2112
|
+
evidence_control_ids=evidence_control_list,
|
|
2113
|
+
evidence_frequency=evidence_frequency,
|
|
2114
|
+
use_security_hub=use_security_hub,
|
|
2115
|
+
force_refresh=force_refresh,
|
|
2116
|
+
)
|
|
2117
|
+
|
|
2118
|
+
scanner.sync_compliance()
|
|
2119
|
+
|
|
2120
|
+
logger.info("AWS Config compliance sync completed successfully")
|
|
2121
|
+
|
|
2122
|
+
except Exception as e:
|
|
2123
|
+
logger.error(f"Error syncing AWS Config compliance: {e}", exc_info=True)
|
|
2124
|
+
raise click.ClickException(str(e))
|
|
2125
|
+
|
|
2126
|
+
|
|
2127
|
+
@awsv2.command(name="sync_kms")
|
|
2128
|
+
@click.option(
|
|
2129
|
+
"--region",
|
|
2130
|
+
type=str,
|
|
2131
|
+
default=os.environ.get("AWS_REGION", "us-east-1"),
|
|
2132
|
+
help="AWS region to collect KMS key data from",
|
|
2133
|
+
)
|
|
2134
|
+
@click.option(
|
|
2135
|
+
"--regscale_id",
|
|
2136
|
+
"--id",
|
|
2137
|
+
type=click.INT,
|
|
2138
|
+
help="RegScale SSP ID to create evidence and compliance assessments under.",
|
|
2139
|
+
required=True,
|
|
2140
|
+
)
|
|
2141
|
+
@click.option(
|
|
2142
|
+
"--session-name",
|
|
2143
|
+
type=str,
|
|
2144
|
+
required=False,
|
|
2145
|
+
help="Name of cached AWS session to use (from 'regscale aws auth login')",
|
|
2146
|
+
)
|
|
2147
|
+
@click.option(
|
|
2148
|
+
"--profile",
|
|
2149
|
+
type=str,
|
|
2150
|
+
required=False,
|
|
2151
|
+
help="AWS profile name from ~/.aws/credentials",
|
|
2152
|
+
envvar="AWS_PROFILE",
|
|
2153
|
+
)
|
|
2154
|
+
@click.option(
|
|
2155
|
+
"--aws_access_key_id",
|
|
2156
|
+
type=str,
|
|
2157
|
+
required=False,
|
|
2158
|
+
help="AWS access key ID (overrides profile)",
|
|
2159
|
+
envvar="AWS_ACCESS_KEY_ID",
|
|
2160
|
+
)
|
|
2161
|
+
@click.option(
|
|
2162
|
+
"--aws_secret_access_key",
|
|
2163
|
+
type=str,
|
|
2164
|
+
required=False,
|
|
2165
|
+
help="AWS secret access key (overrides profile)",
|
|
2166
|
+
default=os.getenv("AWS_SECRET_ACCESS_KEY"),
|
|
2167
|
+
)
|
|
2168
|
+
@click.option(
|
|
2169
|
+
"--aws_session_token",
|
|
2170
|
+
type=click.STRING,
|
|
2171
|
+
required=False,
|
|
2172
|
+
help="AWS session token (overrides profile)",
|
|
2173
|
+
default=os.environ.get("AWS_SESSION_TOKEN"),
|
|
2174
|
+
)
|
|
2175
|
+
@click.option(
|
|
2176
|
+
"--account-id",
|
|
2177
|
+
type=str,
|
|
2178
|
+
required=False,
|
|
2179
|
+
help="Filter KMS keys by AWS account ID (extracted from key ARN)",
|
|
2180
|
+
)
|
|
2181
|
+
@click.option(
|
|
2182
|
+
"--tags",
|
|
2183
|
+
type=str,
|
|
2184
|
+
required=False,
|
|
2185
|
+
help="Filter KMS keys by tags (format: key1=value1,key2=value2). All specified tags must match (AND logic).",
|
|
2186
|
+
)
|
|
2187
|
+
@click.option(
|
|
2188
|
+
"--framework",
|
|
2189
|
+
type=click.Choice(["NIST800-53R5", "ISO27001"], case_sensitive=False),
|
|
2190
|
+
default="NIST800-53R5",
|
|
2191
|
+
help="Compliance framework for KMS key assessments (default: NIST800-53R5)",
|
|
2192
|
+
)
|
|
2193
|
+
@click.option(
|
|
2194
|
+
"--create-issues/--no-create-issues",
|
|
2195
|
+
default=True,
|
|
2196
|
+
help="Create issues for non-compliant KMS keys (default: True)",
|
|
2197
|
+
)
|
|
2198
|
+
@click.option(
|
|
2199
|
+
"--update-control-status/--no-update-control-status",
|
|
2200
|
+
default=True,
|
|
2201
|
+
help="Update control implementation status based on KMS compliance results (default: True)",
|
|
2202
|
+
)
|
|
2203
|
+
@click.option(
|
|
2204
|
+
"--create-poams",
|
|
2205
|
+
"-cp",
|
|
2206
|
+
is_flag=True,
|
|
2207
|
+
default=False,
|
|
2208
|
+
help="Mark created issues as POAMs (default: False)",
|
|
2209
|
+
)
|
|
2210
|
+
@click.option(
|
|
2211
|
+
"--collect-evidence/--no-collect-evidence",
|
|
2212
|
+
default=False,
|
|
2213
|
+
help="Collect and store KMS key evidence artifacts (default: False)",
|
|
2214
|
+
)
|
|
2215
|
+
@click.option(
|
|
2216
|
+
"--evidence-as-attachments/--evidence-as-records",
|
|
2217
|
+
"evidence_as_attachments",
|
|
2218
|
+
default=True,
|
|
2219
|
+
help="Attach evidence files to SSP (default) vs create individual Evidence records",
|
|
2220
|
+
)
|
|
2221
|
+
@click.option(
|
|
2222
|
+
"--evidence-control-ids",
|
|
2223
|
+
type=str,
|
|
2224
|
+
required=False,
|
|
2225
|
+
help="Comma-separated list of control IDs to collect evidence for (e.g., 'SC-12,SC-13,SC-28')",
|
|
2226
|
+
)
|
|
2227
|
+
@click.option(
|
|
2228
|
+
"--evidence-frequency",
|
|
2229
|
+
type=int,
|
|
2230
|
+
default=30,
|
|
2231
|
+
help="Evidence update frequency in days (default: 30)",
|
|
2232
|
+
)
|
|
2233
|
+
@click.option(
|
|
2234
|
+
"--force-refresh",
|
|
2235
|
+
"-f",
|
|
2236
|
+
is_flag=True,
|
|
2237
|
+
default=False,
|
|
2238
|
+
help="Force refresh KMS data by bypassing cache (cache TTL: 4 hours)",
|
|
2239
|
+
)
|
|
2240
|
+
@click.pass_context
|
|
2241
|
+
def sync_kms(ctx, **kwargs) -> None:
|
|
2242
|
+
"""
|
|
2243
|
+
Sync AWS KMS encryption key data to RegScale for compliance evidence and control assessments.
|
|
2244
|
+
|
|
2245
|
+
This command collects AWS KMS key metadata, rotation status, and policies, then:
|
|
2246
|
+
- Assesses each key against NIST 800-53 controls (SC-12, SC-13, SC-28)
|
|
2247
|
+
- Creates control assessments in RegScale with PASS/FAIL status
|
|
2248
|
+
- Creates issues for non-compliant keys (e.g., rotation disabled)
|
|
2249
|
+
- Optionally collects evidence artifacts for compliance documentation
|
|
2250
|
+
|
|
2251
|
+
KMS Compliance Checks:
|
|
2252
|
+
- SC-12 (Key Management): Key rotation enabled, proper lifecycle management
|
|
2253
|
+
- SC-13 (Cryptographic Protection): FIPS-validated algorithms, approved key specs
|
|
2254
|
+
- SC-28 (Data at Rest): Keys enabled and available for encryption
|
|
2255
|
+
|
|
2256
|
+
Filtering Options:
|
|
2257
|
+
Use --account-id to filter keys by AWS account (extracted from key ARN).
|
|
2258
|
+
Use --tags to filter keys by tags (format: key1=value1,key2=value2).
|
|
2259
|
+
Both filters use AND logic - all criteria must match.
|
|
2260
|
+
|
|
2261
|
+
Evidence Collection:
|
|
2262
|
+
Use --collect-evidence to create evidence artifacts for compliance documentation.
|
|
2263
|
+
By default, evidence is attached directly to the SSP as JSONL.GZ files.
|
|
2264
|
+
Use --evidence-as-records to create individual Evidence records instead.
|
|
2265
|
+
Optionally filter evidence by control IDs with --evidence-control-ids.
|
|
2266
|
+
|
|
2267
|
+
Caching:
|
|
2268
|
+
KMS data is cached for 4 hours to reduce API calls. Use --force-refresh to bypass cache.
|
|
2269
|
+
|
|
2270
|
+
Authentication methods (in priority order):
|
|
2271
|
+
1. Cached session: --session-name (from 'regscale aws auth login')
|
|
2272
|
+
2. Explicit credentials: --aws_access_key_id + --aws_secret_access_key
|
|
2273
|
+
3. AWS profile: --profile
|
|
2274
|
+
4. Environment variables or default AWS credential chain
|
|
2275
|
+
|
|
2276
|
+
Examples:
|
|
2277
|
+
# Basic compliance sync with evidence collection
|
|
2278
|
+
regscale aws sync_kms --regscale_id 123 --collect-evidence
|
|
2279
|
+
|
|
2280
|
+
# Filter by account and tags
|
|
2281
|
+
regscale aws sync_kms --regscale_id 123 --account-id 123456789012 \\
|
|
2282
|
+
--tags Environment=prod,Team=security
|
|
2283
|
+
|
|
2284
|
+
# Create individual evidence records (not SSP attachments)
|
|
2285
|
+
regscale aws sync_kms --regscale_id 123 --collect-evidence \\
|
|
2286
|
+
--evidence-as-records
|
|
2287
|
+
|
|
2288
|
+
# Force refresh to bypass cache
|
|
2289
|
+
regscale aws sync_kms --regscale_id 123 --force-refresh
|
|
2290
|
+
"""
|
|
2291
|
+
try:
|
|
2292
|
+
# Extract parameters from kwargs
|
|
2293
|
+
region = kwargs["region"]
|
|
2294
|
+
regscale_id = kwargs["regscale_id"]
|
|
2295
|
+
session_name = kwargs.get("session_name")
|
|
2296
|
+
profile = kwargs.get("profile")
|
|
2297
|
+
aws_access_key_id = kwargs.get("aws_access_key_id")
|
|
2298
|
+
aws_secret_access_key = kwargs.get("aws_secret_access_key")
|
|
2299
|
+
aws_session_token = kwargs.get("aws_session_token")
|
|
2300
|
+
account_id = kwargs.get("account_id")
|
|
2301
|
+
tags = kwargs.get("tags")
|
|
2302
|
+
framework = kwargs.get("framework", "NIST800-53R5")
|
|
2303
|
+
create_issues = kwargs.get("create_issues", True)
|
|
2304
|
+
update_control_status = kwargs.get("update_control_status", True)
|
|
2305
|
+
create_poams = kwargs.get("create_poams", False)
|
|
2306
|
+
collect_evidence = kwargs.get("collect_evidence", False)
|
|
2307
|
+
evidence_as_attachments = kwargs.get("evidence_as_attachments", True)
|
|
2308
|
+
evidence_control_ids = kwargs.get("evidence_control_ids")
|
|
2309
|
+
evidence_frequency = kwargs.get("evidence_frequency", 30)
|
|
2310
|
+
force_refresh = kwargs.get("force_refresh", False)
|
|
2311
|
+
|
|
2312
|
+
logger.info("Starting AWS KMS compliance sync to RegScale...")
|
|
2313
|
+
|
|
2314
|
+
# Resolve AWS credentials
|
|
2315
|
+
resolved_profile, access_key, secret_key, session_token, resolved_region = resolve_aws_credentials(
|
|
2316
|
+
session_name,
|
|
2317
|
+
profile,
|
|
2318
|
+
aws_access_key_id,
|
|
2319
|
+
aws_secret_access_key,
|
|
2320
|
+
aws_session_token,
|
|
2321
|
+
region,
|
|
2322
|
+
)
|
|
2323
|
+
|
|
2324
|
+
# Log credential resolution results
|
|
2325
|
+
logger.info(
|
|
2326
|
+
f"Using AWS credentials - profile: {resolved_profile if resolved_profile else 'not set'}, "
|
|
2327
|
+
f"explicit credentials: {'yes' if access_key else 'no'}, region: {resolved_region}"
|
|
2328
|
+
)
|
|
2329
|
+
|
|
2330
|
+
# Parse tags
|
|
2331
|
+
parsed_tags = parse_tags(tags)
|
|
2332
|
+
|
|
2333
|
+
# Log filtering information
|
|
2334
|
+
if account_id:
|
|
2335
|
+
logger.info(f"Filtering KMS keys by account ID: {account_id}")
|
|
2336
|
+
if parsed_tags:
|
|
2337
|
+
logger.info(f"Filtering KMS keys by tags: {parsed_tags}")
|
|
2338
|
+
|
|
2339
|
+
# Parse evidence control IDs
|
|
2340
|
+
evidence_control_list = None
|
|
2341
|
+
if evidence_control_ids:
|
|
2342
|
+
evidence_control_list = [cid.strip() for cid in evidence_control_ids.split(",")]
|
|
2343
|
+
logger.info(f"Evidence collection requested for controls: {evidence_control_list}")
|
|
2344
|
+
|
|
2345
|
+
# Log evidence collection mode
|
|
2346
|
+
if collect_evidence:
|
|
2347
|
+
if evidence_as_attachments:
|
|
2348
|
+
logger.info(EVIDENCE_MODE_SSP_ATTACHMENTS)
|
|
2349
|
+
else:
|
|
2350
|
+
logger.info(EVIDENCE_MODE_INDIVIDUAL_RECORDS)
|
|
2351
|
+
|
|
2352
|
+
# Import and create KMS evidence integration
|
|
2353
|
+
from .kms_evidence import AWSKMSEvidenceIntegration, KMSEvidenceConfig
|
|
2354
|
+
|
|
2355
|
+
# Create configuration object
|
|
2356
|
+
config = KMSEvidenceConfig(
|
|
2357
|
+
plan_id=regscale_id,
|
|
2358
|
+
region=resolved_region,
|
|
2359
|
+
framework=framework,
|
|
2360
|
+
create_issues=create_issues,
|
|
2361
|
+
update_control_status=update_control_status,
|
|
2362
|
+
create_poams=create_poams,
|
|
2363
|
+
collect_evidence=collect_evidence,
|
|
2364
|
+
evidence_as_attachments=evidence_as_attachments,
|
|
2365
|
+
evidence_control_ids=evidence_control_list,
|
|
2366
|
+
evidence_frequency=evidence_frequency,
|
|
2367
|
+
force_refresh=force_refresh,
|
|
2368
|
+
account_id=account_id,
|
|
2369
|
+
tags=parsed_tags,
|
|
2370
|
+
profile=resolved_profile,
|
|
2371
|
+
aws_access_key_id=access_key,
|
|
2372
|
+
aws_secret_access_key=secret_key,
|
|
2373
|
+
aws_session_token=session_token,
|
|
2374
|
+
)
|
|
2375
|
+
|
|
2376
|
+
scanner = AWSKMSEvidenceIntegration(config)
|
|
2377
|
+
|
|
2378
|
+
scanner.sync_compliance()
|
|
2379
|
+
|
|
2380
|
+
logger.info("AWS KMS compliance sync completed successfully")
|
|
2381
|
+
|
|
2382
|
+
except Exception as e:
|
|
2383
|
+
logger.error(f"Error syncing AWS KMS compliance: {e}", exc_info=True)
|
|
2384
|
+
raise click.ClickException(str(e))
|
|
2385
|
+
|
|
2386
|
+
|
|
2387
|
+
@awsv2.command(name="sync_org")
|
|
2388
|
+
@click.option(
|
|
2389
|
+
"--region",
|
|
2390
|
+
type=str,
|
|
2391
|
+
default=os.environ.get("AWS_REGION", "us-east-1"),
|
|
2392
|
+
help="AWS region for Organizations API access",
|
|
2393
|
+
)
|
|
2394
|
+
@click.option(
|
|
2395
|
+
"--regscale_id",
|
|
2396
|
+
"--id",
|
|
2397
|
+
type=click.INT,
|
|
2398
|
+
help="RegScale SSP ID to create evidence and compliance assessments under.",
|
|
2399
|
+
required=True,
|
|
2400
|
+
)
|
|
2401
|
+
@click.option(
|
|
2402
|
+
"--session-name",
|
|
2403
|
+
type=str,
|
|
2404
|
+
required=False,
|
|
2405
|
+
help="Name of cached AWS session to use (from 'regscale aws auth login')",
|
|
2406
|
+
)
|
|
2407
|
+
@click.option(
|
|
2408
|
+
"--profile",
|
|
2409
|
+
type=str,
|
|
2410
|
+
required=False,
|
|
2411
|
+
help="AWS profile name from ~/.aws/credentials",
|
|
2412
|
+
envvar="AWS_PROFILE",
|
|
2413
|
+
)
|
|
2414
|
+
@click.option(
|
|
2415
|
+
"--aws_access_key_id",
|
|
2416
|
+
type=str,
|
|
2417
|
+
required=False,
|
|
2418
|
+
help="AWS access key ID (overrides profile)",
|
|
2419
|
+
envvar="AWS_ACCESS_KEY_ID",
|
|
2420
|
+
)
|
|
2421
|
+
@click.option(
|
|
2422
|
+
"--aws_secret_access_key",
|
|
2423
|
+
type=str,
|
|
2424
|
+
required=False,
|
|
2425
|
+
help="AWS secret access key (overrides profile)",
|
|
2426
|
+
default=os.getenv("AWS_SECRET_ACCESS_KEY"),
|
|
2427
|
+
)
|
|
2428
|
+
@click.option(
|
|
2429
|
+
"--aws_session_token",
|
|
2430
|
+
type=click.STRING,
|
|
2431
|
+
required=False,
|
|
2432
|
+
help="AWS session token (overrides profile)",
|
|
2433
|
+
default=os.environ.get("AWS_SESSION_TOKEN"),
|
|
2434
|
+
)
|
|
2435
|
+
@click.option(
|
|
2436
|
+
"--framework",
|
|
2437
|
+
type=click.Choice(["NIST800-53R5", "ISO27001"], case_sensitive=False),
|
|
2438
|
+
default="NIST800-53R5",
|
|
2439
|
+
help="Compliance framework for Organizations assessments (default: NIST800-53R5)",
|
|
2440
|
+
)
|
|
2441
|
+
@click.option(
|
|
2442
|
+
"--create-issues/--no-create-issues",
|
|
2443
|
+
default=True,
|
|
2444
|
+
help="Create issues for non-compliant organization (default: True)",
|
|
2445
|
+
)
|
|
2446
|
+
@click.option(
|
|
2447
|
+
"--update-control-status/--no-update-control-status",
|
|
2448
|
+
default=True,
|
|
2449
|
+
help="Update control implementation status based on Organizations compliance (default: True)",
|
|
2450
|
+
)
|
|
2451
|
+
@click.option(
|
|
2452
|
+
"--create-poams",
|
|
2453
|
+
"-cp",
|
|
2454
|
+
is_flag=True,
|
|
2455
|
+
default=False,
|
|
2456
|
+
help="Mark created issues as POAMs (default: False)",
|
|
2457
|
+
)
|
|
2458
|
+
@click.option(
|
|
2459
|
+
"--collect-evidence/--no-collect-evidence",
|
|
2460
|
+
default=False,
|
|
2461
|
+
help="Collect and store Organizations evidence artifacts (default: False)",
|
|
2462
|
+
)
|
|
2463
|
+
@click.option(
|
|
2464
|
+
"--evidence-as-attachments/--evidence-as-records",
|
|
2465
|
+
"evidence_as_attachments",
|
|
2466
|
+
default=True,
|
|
2467
|
+
help="Attach evidence files to SSP (default) vs create individual Evidence records",
|
|
2468
|
+
)
|
|
2469
|
+
@click.option(
|
|
2470
|
+
"--evidence-control-ids",
|
|
2471
|
+
type=str,
|
|
2472
|
+
required=False,
|
|
2473
|
+
help="Comma-separated list of control IDs to collect evidence for (e.g., 'AC-1,PM-9,AC-2')",
|
|
2474
|
+
)
|
|
2475
|
+
@click.option(
|
|
2476
|
+
"--evidence-frequency",
|
|
2477
|
+
type=int,
|
|
2478
|
+
default=30,
|
|
2479
|
+
help="Evidence update frequency in days (default: 30)",
|
|
2480
|
+
)
|
|
2481
|
+
@click.option(
|
|
2482
|
+
"--force-refresh",
|
|
2483
|
+
"-f",
|
|
2484
|
+
is_flag=True,
|
|
2485
|
+
default=False,
|
|
2486
|
+
help="Force refresh Organizations data by bypassing cache (cache TTL: 4 hours)",
|
|
2487
|
+
)
|
|
2488
|
+
@click.pass_context
|
|
2489
|
+
def sync_org(ctx, **kwargs) -> None:
|
|
2490
|
+
"""
|
|
2491
|
+
Sync AWS Organizations data to RegScale for governance and compliance evidence.
|
|
2492
|
+
|
|
2493
|
+
This command collects AWS Organizations structure, accounts, OUs, and SCPs, then:
|
|
2494
|
+
- Assesses organization against NIST 800-53 controls (AC-1, PM-9, AC-2, AC-6)
|
|
2495
|
+
- Creates control assessments in RegScale with PASS/FAIL status
|
|
2496
|
+
- Creates issues for non-compliant configurations
|
|
2497
|
+
- Optionally collects evidence artifacts for compliance documentation
|
|
2498
|
+
|
|
2499
|
+
Organizations Compliance Checks:
|
|
2500
|
+
- AC-1 (Access Control Policy): SCP enforcement, OU structure
|
|
2501
|
+
- PM-9 (Risk Management): Environment separation, restrictive SCPs
|
|
2502
|
+
- AC-2 (Account Management): Account status, contact information
|
|
2503
|
+
- AC-6 (Least Privilege): Restrictive SCPs, deny policies
|
|
2504
|
+
|
|
2505
|
+
Evidence Collection:
|
|
2506
|
+
Use --collect-evidence to create evidence artifacts for compliance documentation.
|
|
2507
|
+
By default, evidence is attached directly to the SSP as JSONL.GZ files.
|
|
2508
|
+
Use --evidence-as-records to create individual Evidence records instead.
|
|
2509
|
+
|
|
2510
|
+
Caching:
|
|
2511
|
+
Organizations data is cached for 4 hours. Use --force-refresh to bypass cache.
|
|
2512
|
+
|
|
2513
|
+
Authentication:
|
|
2514
|
+
1. Cached session: --session-name (from 'regscale aws auth login')
|
|
2515
|
+
2. Explicit credentials: --aws_access_key_id + --aws_secret_access_key
|
|
2516
|
+
3. AWS profile: --profile
|
|
2517
|
+
4. Environment variables or default AWS credential chain
|
|
2518
|
+
|
|
2519
|
+
Examples:
|
|
2520
|
+
# Basic compliance sync with evidence
|
|
2521
|
+
regscale aws sync_org --regscale_id 123 --collect-evidence
|
|
2522
|
+
|
|
2523
|
+
# Create individual evidence records
|
|
2524
|
+
regscale aws sync_org --regscale_id 123 --collect-evidence --evidence-as-records
|
|
2525
|
+
|
|
2526
|
+
# Force refresh with specific controls
|
|
2527
|
+
regscale aws sync_org --regscale_id 123 --collect-evidence \\
|
|
2528
|
+
--evidence-control-ids AC-1,PM-9 --force-refresh
|
|
2529
|
+
"""
|
|
2530
|
+
try:
|
|
2531
|
+
# Extract parameters from kwargs
|
|
2532
|
+
region = kwargs["region"]
|
|
2533
|
+
regscale_id = kwargs["regscale_id"]
|
|
2534
|
+
session_name = kwargs.get("session_name")
|
|
2535
|
+
profile = kwargs.get("profile")
|
|
2536
|
+
aws_access_key_id = kwargs.get("aws_access_key_id")
|
|
2537
|
+
aws_secret_access_key = kwargs.get("aws_secret_access_key")
|
|
2538
|
+
aws_session_token = kwargs.get("aws_session_token")
|
|
2539
|
+
framework = kwargs.get("framework", "NIST800-53R5")
|
|
2540
|
+
create_issues = kwargs.get("create_issues", True)
|
|
2541
|
+
update_control_status = kwargs.get("update_control_status", True)
|
|
2542
|
+
create_poams = kwargs.get("create_poams", False)
|
|
2543
|
+
collect_evidence = kwargs.get("collect_evidence", False)
|
|
2544
|
+
evidence_as_attachments = kwargs.get("evidence_as_attachments", True)
|
|
2545
|
+
evidence_control_ids = kwargs.get("evidence_control_ids")
|
|
2546
|
+
evidence_frequency = kwargs.get("evidence_frequency", 30)
|
|
2547
|
+
force_refresh = kwargs.get("force_refresh", False)
|
|
2548
|
+
|
|
2549
|
+
logger.info("Starting AWS Organizations compliance sync to RegScale...")
|
|
2550
|
+
|
|
2551
|
+
# Resolve credentials
|
|
2552
|
+
resolved_profile, access_key, secret_key, session_token, resolved_region = resolve_aws_credentials(
|
|
2553
|
+
session_name, profile, aws_access_key_id, aws_secret_access_key, aws_session_token, region
|
|
2554
|
+
)
|
|
2555
|
+
|
|
2556
|
+
logger.info(
|
|
2557
|
+
f"Using AWS credentials - profile: {resolved_profile if resolved_profile else 'not set'}, "
|
|
2558
|
+
f"explicit credentials: {'yes' if access_key else 'no'}, region: {resolved_region}"
|
|
2559
|
+
)
|
|
2560
|
+
|
|
2561
|
+
# Parse evidence control IDs
|
|
2562
|
+
evidence_control_list = None
|
|
2563
|
+
if evidence_control_ids:
|
|
2564
|
+
evidence_control_list = [cid.strip() for cid in evidence_control_ids.split(",")]
|
|
2565
|
+
logger.info(f"Evidence collection requested for controls: {evidence_control_list}")
|
|
2566
|
+
|
|
2567
|
+
# Log evidence collection mode
|
|
2568
|
+
if collect_evidence:
|
|
2569
|
+
if evidence_as_attachments:
|
|
2570
|
+
logger.info(EVIDENCE_MODE_SSP_ATTACHMENTS)
|
|
2571
|
+
else:
|
|
2572
|
+
logger.info(EVIDENCE_MODE_INDIVIDUAL_RECORDS)
|
|
2573
|
+
|
|
2574
|
+
# Import and create Organizations evidence integration
|
|
2575
|
+
from .org_evidence import AWSOrganizationsEvidenceIntegration
|
|
2576
|
+
|
|
2577
|
+
scanner = AWSOrganizationsEvidenceIntegration(
|
|
2578
|
+
plan_id=regscale_id,
|
|
2579
|
+
region=resolved_region,
|
|
2580
|
+
framework=framework,
|
|
2581
|
+
create_issues=create_issues,
|
|
2582
|
+
update_control_status=update_control_status,
|
|
2583
|
+
create_poams=create_poams,
|
|
2584
|
+
collect_evidence=collect_evidence,
|
|
2585
|
+
evidence_as_attachments=evidence_as_attachments,
|
|
2586
|
+
evidence_control_ids=evidence_control_list,
|
|
2587
|
+
evidence_frequency=evidence_frequency,
|
|
2588
|
+
force_refresh=force_refresh,
|
|
2589
|
+
profile=resolved_profile,
|
|
2590
|
+
aws_access_key_id=access_key,
|
|
2591
|
+
aws_secret_access_key=secret_key,
|
|
2592
|
+
aws_session_token=session_token,
|
|
2593
|
+
)
|
|
2594
|
+
|
|
2595
|
+
scanner.sync_compliance()
|
|
2596
|
+
|
|
2597
|
+
logger.info("AWS Organizations compliance sync completed successfully")
|
|
2598
|
+
|
|
2599
|
+
except Exception as e:
|
|
2600
|
+
logger.error(f"Error syncing AWS Organizations compliance: {e}", exc_info=True)
|
|
2601
|
+
raise click.ClickException(str(e))
|
|
2602
|
+
|
|
2603
|
+
|
|
2604
|
+
@awsv2.command(name="sync_iam")
|
|
2605
|
+
@click.option(
|
|
2606
|
+
"--region",
|
|
2607
|
+
type=str,
|
|
2608
|
+
default=os.environ.get("AWS_REGION", "us-east-1"),
|
|
2609
|
+
help="AWS region for IAM API access",
|
|
2610
|
+
)
|
|
2611
|
+
@click.option(
|
|
2612
|
+
"--regscale_id",
|
|
2613
|
+
"--id",
|
|
2614
|
+
type=click.INT,
|
|
2615
|
+
help="RegScale SSP ID to create evidence and compliance assessments under.",
|
|
2616
|
+
required=True,
|
|
2617
|
+
)
|
|
2618
|
+
@click.option(
|
|
2619
|
+
"--session-name",
|
|
2620
|
+
type=str,
|
|
2621
|
+
required=False,
|
|
2622
|
+
help="Name of cached AWS session to use (from 'regscale aws auth login')",
|
|
2623
|
+
)
|
|
2624
|
+
@click.option(
|
|
2625
|
+
"--profile",
|
|
2626
|
+
type=str,
|
|
2627
|
+
required=False,
|
|
2628
|
+
help="AWS profile name from ~/.aws/credentials",
|
|
2629
|
+
envvar="AWS_PROFILE",
|
|
2630
|
+
)
|
|
2631
|
+
@click.option(
|
|
2632
|
+
"--aws_access_key_id",
|
|
2633
|
+
type=str,
|
|
2634
|
+
required=False,
|
|
2635
|
+
help="AWS access key ID (overrides profile)",
|
|
2636
|
+
envvar="AWS_ACCESS_KEY_ID",
|
|
2637
|
+
)
|
|
2638
|
+
@click.option(
|
|
2639
|
+
"--aws_secret_access_key",
|
|
2640
|
+
type=str,
|
|
2641
|
+
required=False,
|
|
2642
|
+
help="AWS secret access key (overrides profile)",
|
|
2643
|
+
default=os.getenv("AWS_SECRET_ACCESS_KEY"),
|
|
2644
|
+
)
|
|
2645
|
+
@click.option(
|
|
2646
|
+
"--aws_session_token",
|
|
2647
|
+
type=click.STRING,
|
|
2648
|
+
required=False,
|
|
2649
|
+
help="AWS session token (overrides profile)",
|
|
2650
|
+
default=os.environ.get("AWS_SESSION_TOKEN"),
|
|
2651
|
+
)
|
|
2652
|
+
@click.option(
|
|
2653
|
+
"--framework",
|
|
2654
|
+
type=click.Choice(["NIST800-53R5", "ISO27001"], case_sensitive=False),
|
|
2655
|
+
default="NIST800-53R5",
|
|
2656
|
+
help="Compliance framework for IAM assessments (default: NIST800-53R5)",
|
|
2657
|
+
)
|
|
2658
|
+
@click.option(
|
|
2659
|
+
"--create-issues/--no-create-issues",
|
|
2660
|
+
default=True,
|
|
2661
|
+
help="Create issues for non-compliant IAM configurations (default: True)",
|
|
2662
|
+
)
|
|
2663
|
+
@click.option(
|
|
2664
|
+
"--update-control-status/--no-update-control-status",
|
|
2665
|
+
default=True,
|
|
2666
|
+
help="Update control implementation status based on IAM compliance (default: True)",
|
|
2667
|
+
)
|
|
2668
|
+
@click.option(
|
|
2669
|
+
"--create-poams",
|
|
2670
|
+
"-cp",
|
|
2671
|
+
is_flag=True,
|
|
2672
|
+
default=False,
|
|
2673
|
+
help="Mark created issues as POAMs (default: False)",
|
|
2674
|
+
)
|
|
2675
|
+
@click.option(
|
|
2676
|
+
"--collect-evidence/--no-collect-evidence",
|
|
2677
|
+
default=False,
|
|
2678
|
+
help="Collect and store IAM evidence artifacts (default: False)",
|
|
2679
|
+
)
|
|
2680
|
+
@click.option(
|
|
2681
|
+
"--evidence-as-attachments/--evidence-as-records",
|
|
2682
|
+
"evidence_as_attachments",
|
|
2683
|
+
default=True,
|
|
2684
|
+
help="Attach evidence files to SSP (default) vs create individual Evidence records",
|
|
2685
|
+
)
|
|
2686
|
+
@click.option(
|
|
2687
|
+
"--evidence-control-ids",
|
|
2688
|
+
type=str,
|
|
2689
|
+
required=False,
|
|
2690
|
+
help="Comma-separated list of control IDs to collect evidence for (e.g., 'AC-2,IA-2,AC-6')",
|
|
2691
|
+
)
|
|
2692
|
+
@click.option(
|
|
2693
|
+
"--evidence-frequency",
|
|
2694
|
+
type=int,
|
|
2695
|
+
default=30,
|
|
2696
|
+
help="Evidence update frequency in days (default: 30)",
|
|
2697
|
+
)
|
|
2698
|
+
@click.option(
|
|
2699
|
+
"--force-refresh",
|
|
2700
|
+
"-f",
|
|
2701
|
+
is_flag=True,
|
|
2702
|
+
default=False,
|
|
2703
|
+
help="Force refresh IAM data by bypassing cache (cache TTL: 4 hours)",
|
|
2704
|
+
)
|
|
2705
|
+
@click.pass_context
|
|
2706
|
+
def sync_iam(ctx, **kwargs) -> None:
|
|
2707
|
+
"""
|
|
2708
|
+
Sync AWS IAM data to RegScale for access control and authentication evidence.
|
|
2709
|
+
|
|
2710
|
+
This command collects AWS IAM users, groups, roles, and policies, then:
|
|
2711
|
+
- Assesses IAM against NIST 800-53 controls (AC-2, AC-6, IA-2, IA-5, AC-3)
|
|
2712
|
+
- Creates control assessments in RegScale with PASS/FAIL status
|
|
2713
|
+
- Creates issues for non-compliant configurations
|
|
2714
|
+
- Optionally collects evidence artifacts for compliance documentation
|
|
2715
|
+
|
|
2716
|
+
IAM Compliance Checks:
|
|
2717
|
+
- AC-2 (Account Management): MFA enforcement, root account security
|
|
2718
|
+
- AC-6 (Least Privilege): No users with AdministratorAccess
|
|
2719
|
+
- IA-2 (Authentication): Strong password policy, MFA required
|
|
2720
|
+
- IA-5 (Authenticator Management): Access key rotation, unused credentials
|
|
2721
|
+
- AC-3 (Access Enforcement): Restrictive role trust policies
|
|
2722
|
+
|
|
2723
|
+
Evidence Collection:
|
|
2724
|
+
Use --collect-evidence to create evidence artifacts for compliance documentation.
|
|
2725
|
+
By default, evidence is attached directly to the SSP as JSONL.GZ files.
|
|
2726
|
+
Use --evidence-as-records to create individual Evidence records instead.
|
|
2727
|
+
|
|
2728
|
+
Caching:
|
|
2729
|
+
IAM data is cached for 4 hours. Use --force-refresh to bypass cache.
|
|
2730
|
+
|
|
2731
|
+
Authentication:
|
|
2732
|
+
1. Cached session: --session-name (from 'regscale aws auth login')
|
|
2733
|
+
2. Explicit credentials: --aws_access_key_id + --aws_secret_access_key
|
|
2734
|
+
3. AWS profile: --profile
|
|
2735
|
+
4. Environment variables or default AWS credential chain
|
|
2736
|
+
|
|
2737
|
+
Examples:
|
|
2738
|
+
# Basic compliance sync with evidence
|
|
2739
|
+
regscale aws sync_iam --regscale_id 123 --collect-evidence
|
|
2740
|
+
|
|
2741
|
+
# Create individual evidence records
|
|
2742
|
+
regscale aws sync_iam --regscale_id 123 --collect-evidence --evidence-as-records
|
|
2743
|
+
|
|
2744
|
+
# Force refresh with specific controls
|
|
2745
|
+
regscale aws sync_iam --regscale_id 123 --collect-evidence \\
|
|
2746
|
+
--evidence-control-ids AC-2,IA-2,AC-6 --force-refresh
|
|
2747
|
+
"""
|
|
2748
|
+
try:
|
|
2749
|
+
# Extract parameters from kwargs
|
|
2750
|
+
region = kwargs["region"]
|
|
2751
|
+
regscale_id = kwargs["regscale_id"]
|
|
2752
|
+
session_name = kwargs.get("session_name")
|
|
2753
|
+
profile = kwargs.get("profile")
|
|
2754
|
+
aws_access_key_id = kwargs.get("aws_access_key_id")
|
|
2755
|
+
aws_secret_access_key = kwargs.get("aws_secret_access_key")
|
|
2756
|
+
aws_session_token = kwargs.get("aws_session_token")
|
|
2757
|
+
framework = kwargs.get("framework", "NIST800-53R5")
|
|
2758
|
+
create_issues = kwargs.get("create_issues", True)
|
|
2759
|
+
update_control_status = kwargs.get("update_control_status", True)
|
|
2760
|
+
create_poams = kwargs.get("create_poams", False)
|
|
2761
|
+
collect_evidence = kwargs.get("collect_evidence", False)
|
|
2762
|
+
evidence_as_attachments = kwargs.get("evidence_as_attachments", True)
|
|
2763
|
+
evidence_control_ids = kwargs.get("evidence_control_ids")
|
|
2764
|
+
evidence_frequency = kwargs.get("evidence_frequency", 30)
|
|
2765
|
+
force_refresh = kwargs.get("force_refresh", False)
|
|
2766
|
+
|
|
2767
|
+
logger.info("Starting AWS IAM compliance sync to RegScale...")
|
|
2768
|
+
|
|
2769
|
+
# Resolve credentials
|
|
2770
|
+
resolved_profile, access_key, secret_key, session_token, resolved_region = resolve_aws_credentials(
|
|
2771
|
+
session_name, profile, aws_access_key_id, aws_secret_access_key, aws_session_token, region
|
|
2772
|
+
)
|
|
2773
|
+
|
|
2774
|
+
logger.info(
|
|
2775
|
+
f"Using AWS credentials - profile: {resolved_profile if resolved_profile else 'not set'}, "
|
|
2776
|
+
f"explicit credentials: {'yes' if access_key else 'no'}, region: {resolved_region}"
|
|
2777
|
+
)
|
|
2778
|
+
|
|
2779
|
+
# Parse evidence control IDs
|
|
2780
|
+
evidence_control_list = None
|
|
2781
|
+
if evidence_control_ids:
|
|
2782
|
+
evidence_control_list = [cid.strip() for cid in evidence_control_ids.split(",")]
|
|
2783
|
+
logger.info(f"Evidence collection requested for controls: {evidence_control_list}")
|
|
2784
|
+
|
|
2785
|
+
# Log evidence collection mode
|
|
2786
|
+
if collect_evidence:
|
|
2787
|
+
if evidence_as_attachments:
|
|
2788
|
+
logger.info(EVIDENCE_MODE_SSP_ATTACHMENTS)
|
|
2789
|
+
else:
|
|
2790
|
+
logger.info(EVIDENCE_MODE_INDIVIDUAL_RECORDS)
|
|
2791
|
+
|
|
2792
|
+
# Import and create IAM evidence integration
|
|
2793
|
+
from .iam_evidence import AWSIAMEvidenceIntegration
|
|
2794
|
+
|
|
2795
|
+
scanner = AWSIAMEvidenceIntegration(
|
|
2796
|
+
plan_id=regscale_id,
|
|
2797
|
+
region=resolved_region,
|
|
2798
|
+
framework=framework,
|
|
2799
|
+
create_issues=create_issues,
|
|
2800
|
+
update_control_status=update_control_status,
|
|
2801
|
+
create_poams=create_poams,
|
|
2802
|
+
collect_evidence=collect_evidence,
|
|
2803
|
+
evidence_as_attachments=evidence_as_attachments,
|
|
2804
|
+
evidence_control_ids=evidence_control_list,
|
|
2805
|
+
evidence_frequency=evidence_frequency,
|
|
2806
|
+
force_refresh=force_refresh,
|
|
2807
|
+
profile=resolved_profile,
|
|
2808
|
+
aws_access_key_id=access_key,
|
|
2809
|
+
aws_secret_access_key=secret_key,
|
|
2810
|
+
aws_session_token=session_token,
|
|
2811
|
+
)
|
|
2812
|
+
|
|
2813
|
+
scanner.sync_compliance()
|
|
2814
|
+
|
|
2815
|
+
logger.info("AWS IAM compliance sync completed successfully")
|
|
2816
|
+
|
|
2817
|
+
except Exception as e:
|
|
2818
|
+
logger.error(f"Error syncing AWS IAM compliance: {e}", exc_info=True)
|
|
2819
|
+
raise click.ClickException(str(e))
|
|
2820
|
+
|
|
2821
|
+
|
|
2822
|
+
@awsv2.command(name="sync_guardduty")
|
|
2823
|
+
@click.option(
|
|
2824
|
+
"--region",
|
|
2825
|
+
type=str,
|
|
2826
|
+
default=os.environ.get("AWS_REGION", "us-east-1"),
|
|
2827
|
+
help="AWS region for GuardDuty API access",
|
|
2828
|
+
)
|
|
2829
|
+
@click.option(
|
|
2830
|
+
"--regscale_id",
|
|
2831
|
+
"--id",
|
|
2832
|
+
type=click.INT,
|
|
2833
|
+
help="RegScale SSP ID to create findings and evidence under.",
|
|
2834
|
+
required=True,
|
|
2835
|
+
)
|
|
2836
|
+
@click.option(
|
|
2837
|
+
"--session-name",
|
|
2838
|
+
type=str,
|
|
2839
|
+
required=False,
|
|
2840
|
+
help="Name of cached AWS session to use (from 'regscale aws auth login')",
|
|
2841
|
+
)
|
|
2842
|
+
@click.option(
|
|
2843
|
+
"--profile",
|
|
2844
|
+
type=str,
|
|
2845
|
+
required=False,
|
|
2846
|
+
help="AWS profile name from ~/.aws/credentials",
|
|
2847
|
+
envvar="AWS_PROFILE",
|
|
2848
|
+
)
|
|
2849
|
+
@click.option(
|
|
2850
|
+
"--aws_access_key_id",
|
|
2851
|
+
type=str,
|
|
2852
|
+
required=False,
|
|
2853
|
+
help="AWS access key ID (overrides profile)",
|
|
2854
|
+
envvar="AWS_ACCESS_KEY_ID",
|
|
2855
|
+
)
|
|
2856
|
+
@click.option(
|
|
2857
|
+
"--aws_secret_access_key",
|
|
2858
|
+
type=str,
|
|
2859
|
+
required=False,
|
|
2860
|
+
help="AWS secret access key (overrides profile)",
|
|
2861
|
+
default=os.getenv("AWS_SECRET_ACCESS_KEY"),
|
|
2862
|
+
)
|
|
2863
|
+
@click.option(
|
|
2864
|
+
"--aws_session_token",
|
|
2865
|
+
type=click.STRING,
|
|
2866
|
+
required=False,
|
|
2867
|
+
help="AWS session token (overrides profile)",
|
|
2868
|
+
default=os.environ.get("AWS_SESSION_TOKEN"),
|
|
2869
|
+
)
|
|
2870
|
+
@click.option(
|
|
2871
|
+
"--account-id",
|
|
2872
|
+
type=str,
|
|
2873
|
+
required=False,
|
|
2874
|
+
help="Filter GuardDuty findings by AWS account ID",
|
|
2875
|
+
)
|
|
2876
|
+
@click.option(
|
|
2877
|
+
"--tags",
|
|
2878
|
+
type=str,
|
|
2879
|
+
required=False,
|
|
2880
|
+
help="Filter GuardDuty detectors by tags (format: key1=value1,key2=value2)",
|
|
2881
|
+
)
|
|
2882
|
+
@click.option(
|
|
2883
|
+
"--framework",
|
|
2884
|
+
type=click.Choice(["NIST800-53R5"], case_sensitive=False),
|
|
2885
|
+
default="NIST800-53R5",
|
|
2886
|
+
help="Compliance framework for GuardDuty assessments (default: NIST800-53R5)",
|
|
2887
|
+
)
|
|
2888
|
+
@click.option(
|
|
2889
|
+
"--create-issues/--no-create-issues",
|
|
2890
|
+
default=False,
|
|
2891
|
+
help="Create issues for GuardDuty findings without CVEs (default: False)",
|
|
2892
|
+
)
|
|
2893
|
+
@click.option(
|
|
2894
|
+
"--create-vulnerabilities/--no-create-vulnerabilities",
|
|
2895
|
+
default=False,
|
|
2896
|
+
help="Create vulnerabilities for GuardDuty findings with CVEs (default: False)",
|
|
2897
|
+
)
|
|
2898
|
+
@click.option(
|
|
2899
|
+
"--update-control-status/--no-update-control-status",
|
|
2900
|
+
default=True,
|
|
2901
|
+
help="Update control implementation status based on findings (default: True)",
|
|
2902
|
+
)
|
|
2903
|
+
@click.option(
|
|
2904
|
+
"--create-poams/--no-create-poams",
|
|
2905
|
+
default=False,
|
|
2906
|
+
help="Create POA&Ms for failed controls (default: False)",
|
|
2907
|
+
)
|
|
2908
|
+
@click.option(
|
|
2909
|
+
"--collect-evidence/--no-collect-evidence",
|
|
2910
|
+
default=True,
|
|
2911
|
+
help="Collect and store GuardDuty evidence artifacts (default: True)",
|
|
2912
|
+
)
|
|
2913
|
+
@click.option(
|
|
2914
|
+
"--evidence-as-attachments/--evidence-as-records",
|
|
2915
|
+
"evidence_as_attachments",
|
|
2916
|
+
default=True,
|
|
2917
|
+
help="Attach evidence files to SSP (default) vs create individual Evidence records",
|
|
2918
|
+
)
|
|
2919
|
+
@click.option(
|
|
2920
|
+
"--evidence-control-ids",
|
|
2921
|
+
type=str,
|
|
2922
|
+
required=False,
|
|
2923
|
+
help="Comma-separated list of control IDs to collect evidence for (e.g., 'SI-4,IR-4,IR-5')",
|
|
2924
|
+
)
|
|
2925
|
+
@click.option(
|
|
2926
|
+
"--evidence-frequency",
|
|
2927
|
+
type=int,
|
|
2928
|
+
default=30,
|
|
2929
|
+
help="Evidence update frequency in days (default: 30)",
|
|
2930
|
+
)
|
|
2931
|
+
@click.option(
|
|
2932
|
+
"--force-refresh",
|
|
2933
|
+
"-f",
|
|
2934
|
+
is_flag=True,
|
|
2935
|
+
default=False,
|
|
2936
|
+
help="Force refresh GuardDuty data by bypassing cache (cache TTL: 4 hours)",
|
|
2937
|
+
)
|
|
2938
|
+
@click.pass_context
|
|
2939
|
+
def sync_guardduty(ctx, **kwargs) -> None:
|
|
2940
|
+
"""
|
|
2941
|
+
Sync AWS GuardDuty threat detection data to RegScale as compliance evidence.
|
|
2942
|
+
|
|
2943
|
+
Collects GuardDuty detector configurations and findings for compliance assessment
|
|
2944
|
+
against NIST 800-53 R5 controls. By default, creates evidence attachments only
|
|
2945
|
+
(no issues or vulnerabilities).
|
|
2946
|
+
|
|
2947
|
+
GuardDuty Compliance Checks:
|
|
2948
|
+
- SI-4 (System Monitoring): Detector enabled and configured
|
|
2949
|
+
- IR-4 (Incident Handling): Finding detection and alerting
|
|
2950
|
+
- IR-5 (Incident Monitoring): Continuous threat monitoring
|
|
2951
|
+
- SI-3 (Malicious Code Protection): Malware detection capabilities
|
|
2952
|
+
- RA-5 (Vulnerability Monitoring): Threat intelligence integration
|
|
2953
|
+
|
|
2954
|
+
Examples:
|
|
2955
|
+
# Basic evidence collection (default)
|
|
2956
|
+
regscale aws sync_guardduty --regscale_id 123
|
|
2957
|
+
|
|
2958
|
+
# Collect evidence for specific controls
|
|
2959
|
+
regscale aws sync_guardduty --regscale_id 123 --evidence-control-ids SI-4,IR-4,IR-5
|
|
2960
|
+
|
|
2961
|
+
# Filter by AWS account
|
|
2962
|
+
regscale aws sync_guardduty --regscale_id 123 --account-id 123456789012
|
|
2963
|
+
|
|
2964
|
+
# Create issues/vulnerabilities from findings (non-default)
|
|
2965
|
+
regscale aws sync_guardduty --regscale_id 123 --create-issues --create-vulnerabilities --no-collect-evidence
|
|
2966
|
+
"""
|
|
2967
|
+
try:
|
|
2968
|
+
# Extract parameters from kwargs
|
|
2969
|
+
region = kwargs["region"]
|
|
2970
|
+
regscale_id = kwargs["regscale_id"]
|
|
2971
|
+
session_name = kwargs.get("session_name")
|
|
2972
|
+
profile = kwargs.get("profile")
|
|
2973
|
+
aws_access_key_id = kwargs.get("aws_access_key_id")
|
|
2974
|
+
aws_secret_access_key = kwargs.get("aws_secret_access_key")
|
|
2975
|
+
aws_session_token = kwargs.get("aws_session_token")
|
|
2976
|
+
account_id = kwargs.get("account_id")
|
|
2977
|
+
tags = kwargs.get("tags")
|
|
2978
|
+
framework = kwargs.get("framework", "NIST800-53R5")
|
|
2979
|
+
create_issues = kwargs.get("create_issues", False)
|
|
2980
|
+
create_vulnerabilities = kwargs.get("create_vulnerabilities", False)
|
|
2981
|
+
update_control_status = kwargs.get("update_control_status", True)
|
|
2982
|
+
create_poams = kwargs.get("create_poams", False)
|
|
2983
|
+
collect_evidence = kwargs.get("collect_evidence", True)
|
|
2984
|
+
evidence_as_attachments = kwargs.get("evidence_as_attachments", True)
|
|
2985
|
+
evidence_control_ids = kwargs.get("evidence_control_ids")
|
|
2986
|
+
evidence_frequency = kwargs.get("evidence_frequency", 30)
|
|
2987
|
+
force_refresh = kwargs.get("force_refresh", False)
|
|
2988
|
+
|
|
2989
|
+
logger.info("Starting AWS GuardDuty findings sync to RegScale...")
|
|
2990
|
+
|
|
2991
|
+
resolved_profile, access_key, secret_key, session_token, resolved_region = resolve_aws_credentials(
|
|
2992
|
+
session_name, profile, aws_access_key_id, aws_secret_access_key, aws_session_token, region
|
|
2993
|
+
)
|
|
2994
|
+
|
|
2995
|
+
logger.info(
|
|
2996
|
+
f"Using AWS credentials - profile: {resolved_profile if resolved_profile else 'not set'}, "
|
|
2997
|
+
f"explicit credentials: {'yes' if access_key else 'no'}, region: {resolved_region}"
|
|
2998
|
+
)
|
|
2999
|
+
|
|
3000
|
+
parsed_tags = parse_tags(tags)
|
|
3001
|
+
|
|
3002
|
+
if account_id:
|
|
3003
|
+
logger.info(f"Filtering GuardDuty findings by account ID: {account_id}")
|
|
3004
|
+
if parsed_tags:
|
|
3005
|
+
logger.info(f"Filtering GuardDuty detectors by tags: {parsed_tags}")
|
|
3006
|
+
|
|
3007
|
+
evidence_control_list = None
|
|
3008
|
+
if evidence_control_ids:
|
|
3009
|
+
evidence_control_list = [cid.strip() for cid in evidence_control_ids.split(",")]
|
|
3010
|
+
logger.info(f"Evidence collection requested for controls: {evidence_control_list}")
|
|
3011
|
+
|
|
3012
|
+
if collect_evidence:
|
|
3013
|
+
if evidence_as_attachments:
|
|
3014
|
+
logger.info(EVIDENCE_MODE_SSP_ATTACHMENTS)
|
|
3015
|
+
else:
|
|
3016
|
+
logger.info(EVIDENCE_MODE_INDIVIDUAL_RECORDS)
|
|
3017
|
+
|
|
3018
|
+
from .guardduty_evidence import AWSGuardDutyEvidenceIntegration, GuardDutyEvidenceConfig
|
|
3019
|
+
|
|
3020
|
+
# Create configuration object
|
|
3021
|
+
config = GuardDutyEvidenceConfig(
|
|
3022
|
+
plan_id=regscale_id,
|
|
3023
|
+
region=resolved_region,
|
|
3024
|
+
framework=framework,
|
|
3025
|
+
create_issues=create_issues,
|
|
3026
|
+
create_vulnerabilities=create_vulnerabilities,
|
|
3027
|
+
update_control_status=update_control_status,
|
|
3028
|
+
create_poams=create_poams,
|
|
3029
|
+
collect_evidence=collect_evidence,
|
|
3030
|
+
evidence_as_attachments=evidence_as_attachments,
|
|
3031
|
+
evidence_control_ids=evidence_control_list,
|
|
3032
|
+
evidence_frequency=evidence_frequency,
|
|
3033
|
+
force_refresh=force_refresh,
|
|
3034
|
+
account_id=account_id,
|
|
3035
|
+
tags=parsed_tags,
|
|
3036
|
+
profile=resolved_profile,
|
|
3037
|
+
aws_access_key_id=access_key,
|
|
3038
|
+
aws_secret_access_key=secret_key,
|
|
3039
|
+
aws_session_token=session_token,
|
|
3040
|
+
)
|
|
3041
|
+
|
|
3042
|
+
scanner = AWSGuardDutyEvidenceIntegration(config)
|
|
3043
|
+
|
|
3044
|
+
scanner.sync_findings()
|
|
3045
|
+
|
|
3046
|
+
logger.info("AWS GuardDuty findings sync completed successfully")
|
|
3047
|
+
|
|
3048
|
+
except Exception as e:
|
|
3049
|
+
logger.error(f"Error syncing AWS GuardDuty findings: {e}", exc_info=True)
|
|
3050
|
+
raise click.ClickException(str(e))
|
|
3051
|
+
|
|
3052
|
+
|
|
3053
|
+
@awsv2.command(name="sync_s3")
|
|
3054
|
+
@click.option("--region", default="us-east-1", help="AWS region to collect S3 buckets from")
|
|
3055
|
+
@click.option("--regscale-id", type=int, required=True, help="RegScale SSP plan ID")
|
|
3056
|
+
@click.option("--account-id", help="AWS account ID to filter resources")
|
|
3057
|
+
@click.option("--tags", help="Resource tags for filtering (format: key1=value1,key2=value2)")
|
|
3058
|
+
@click.option("--bucket-name-filter", help="Filter buckets by name prefix/pattern")
|
|
3059
|
+
@click.option("--create-evidence", is_flag=True, default=False, help="Create evidence records in RegScale")
|
|
3060
|
+
@click.option(
|
|
3061
|
+
"--create-ssp-attachment",
|
|
3062
|
+
is_flag=True,
|
|
3063
|
+
default=True,
|
|
3064
|
+
help="Create SSP attachment with evidence (default: True)",
|
|
3065
|
+
)
|
|
3066
|
+
@click.option(
|
|
3067
|
+
"--evidence-control-ids",
|
|
3068
|
+
help="Comma-separated control IDs to link evidence (e.g., SC-13,SC-28,AC-3)",
|
|
3069
|
+
)
|
|
3070
|
+
@click.option("--force-refresh", is_flag=True, default=False, help="Force refresh cached data")
|
|
3071
|
+
@click.option("--session-name", help="Custom session name for this operation")
|
|
3072
|
+
@click.option("--profile", help="AWS profile name to use for authentication")
|
|
3073
|
+
@click.option("--aws-access-key-id", help="AWS access key ID")
|
|
3074
|
+
@click.option("--aws-secret-access-key", help="AWS secret access key")
|
|
3075
|
+
@click.option("--aws-session-token", help="AWS session token")
|
|
3076
|
+
@click.pass_context
|
|
3077
|
+
def sync_s3(ctx, **kwargs):
|
|
3078
|
+
"""
|
|
3079
|
+
Sync AWS S3 storage configurations to RegScale as compliance evidence.
|
|
3080
|
+
|
|
3081
|
+
Collects S3 bucket configurations including encryption, versioning, logging,
|
|
3082
|
+
access controls, and public access settings for compliance assessment against
|
|
3083
|
+
NIST 800-53 R5 controls (SC-13, SC-28, AC-3, AC-6, AU-2, AU-9, CP-9).
|
|
3084
|
+
|
|
3085
|
+
Examples:
|
|
3086
|
+
|
|
3087
|
+
# Sync all S3 buckets in us-east-1
|
|
3088
|
+
regscale aws sync_s3 --region us-east-1 --regscale-id 123
|
|
3089
|
+
|
|
3090
|
+
# Filter by bucket name prefix
|
|
3091
|
+
regscale aws sync_s3 --region us-east-1 --regscale-id 123 --bucket-name-filter prod-
|
|
3092
|
+
|
|
3093
|
+
# Filter by tags
|
|
3094
|
+
regscale aws sync_s3 --region us-east-1 --regscale-id 123 --tags Environment=Production,Compliance=Required
|
|
3095
|
+
|
|
3096
|
+
# Create evidence and link to controls
|
|
3097
|
+
regscale aws sync_s3 --region us-east-1 --regscale-id 123 --create-evidence \\
|
|
3098
|
+
--evidence-control-ids SC-13,SC-28,AC-3
|
|
3099
|
+
|
|
3100
|
+
# Use specific AWS profile
|
|
3101
|
+
regscale aws sync_s3 --region us-west-2 --regscale-id 456 --profile production
|
|
3102
|
+
|
|
3103
|
+
# Force refresh cached data
|
|
3104
|
+
regscale aws sync_s3 --region us-east-1 --regscale-id 123 --force-refresh
|
|
3105
|
+
"""
|
|
3106
|
+
from regscale.integrations.commercial.aws.s3_evidence import AWSS3EvidenceIntegration
|
|
3107
|
+
|
|
3108
|
+
try:
|
|
3109
|
+
# Extract parameters from kwargs
|
|
3110
|
+
region = kwargs["region"]
|
|
3111
|
+
regscale_id = kwargs["regscale_id"]
|
|
3112
|
+
account_id = kwargs.get("account_id")
|
|
3113
|
+
tags = kwargs.get("tags")
|
|
3114
|
+
bucket_name_filter = kwargs.get("bucket_name_filter")
|
|
3115
|
+
create_evidence = kwargs.get("create_evidence", False)
|
|
3116
|
+
create_ssp_attachment = kwargs.get("create_ssp_attachment", True)
|
|
3117
|
+
evidence_control_ids = kwargs.get("evidence_control_ids")
|
|
3118
|
+
force_refresh = kwargs.get("force_refresh", False)
|
|
3119
|
+
session_name = kwargs.get("session_name")
|
|
3120
|
+
profile = kwargs.get("profile")
|
|
3121
|
+
aws_access_key_id = kwargs.get("aws_access_key_id")
|
|
3122
|
+
aws_secret_access_key = kwargs.get("aws_secret_access_key")
|
|
3123
|
+
aws_session_token = kwargs.get("aws_session_token")
|
|
3124
|
+
|
|
3125
|
+
logger.info("Starting AWS S3 storage configuration sync to RegScale...")
|
|
3126
|
+
|
|
3127
|
+
resolved_profile, access_key, secret_key, session_token, resolved_region = resolve_aws_credentials(
|
|
3128
|
+
session_name, profile, aws_access_key_id, aws_secret_access_key, aws_session_token, region
|
|
3129
|
+
)
|
|
3130
|
+
|
|
3131
|
+
logger.info(
|
|
3132
|
+
f"Using AWS credentials - profile: {resolved_profile if resolved_profile else 'not set'}, "
|
|
3133
|
+
f"explicit credentials: {'yes' if access_key else 'no'}, region: {resolved_region}"
|
|
3134
|
+
)
|
|
3135
|
+
|
|
3136
|
+
parsed_tags = parse_tags(tags)
|
|
3137
|
+
|
|
3138
|
+
if account_id:
|
|
3139
|
+
logger.info(f"Filtering S3 buckets by account ID: {account_id}")
|
|
3140
|
+
|
|
3141
|
+
if parsed_tags:
|
|
3142
|
+
logger.info(f"Filtering S3 buckets by tags: {parsed_tags}")
|
|
3143
|
+
|
|
3144
|
+
if bucket_name_filter:
|
|
3145
|
+
logger.info(f"Filtering S3 buckets by name pattern: {bucket_name_filter}")
|
|
3146
|
+
|
|
3147
|
+
control_ids = None
|
|
3148
|
+
if evidence_control_ids:
|
|
3149
|
+
control_ids = [ctrl.strip() for ctrl in evidence_control_ids.split(",")]
|
|
3150
|
+
logger.info(f"Evidence collection requested for controls: {control_ids}")
|
|
3151
|
+
|
|
3152
|
+
if create_evidence or create_ssp_attachment:
|
|
3153
|
+
if create_ssp_attachment:
|
|
3154
|
+
logger.info(EVIDENCE_MODE_SSP_ATTACHMENTS)
|
|
3155
|
+
if create_evidence:
|
|
3156
|
+
logger.info(EVIDENCE_MODE_INDIVIDUAL_RECORDS)
|
|
3157
|
+
|
|
3158
|
+
from .s3_evidence import AWSS3EvidenceIntegration, S3EvidenceConfig
|
|
3159
|
+
|
|
3160
|
+
# Create configuration object
|
|
3161
|
+
config = S3EvidenceConfig(
|
|
3162
|
+
plan_id=regscale_id,
|
|
3163
|
+
region=resolved_region,
|
|
3164
|
+
account_id=account_id,
|
|
3165
|
+
tags=parsed_tags,
|
|
3166
|
+
bucket_name_filter=bucket_name_filter,
|
|
3167
|
+
create_evidence=create_evidence,
|
|
3168
|
+
create_ssp_attachment=create_ssp_attachment,
|
|
3169
|
+
evidence_control_ids=control_ids,
|
|
3170
|
+
force_refresh=force_refresh,
|
|
3171
|
+
aws_profile=resolved_profile,
|
|
3172
|
+
aws_access_key_id=access_key,
|
|
3173
|
+
aws_secret_access_key=secret_key,
|
|
3174
|
+
aws_session_token=session_token,
|
|
3175
|
+
)
|
|
3176
|
+
|
|
3177
|
+
scanner = AWSS3EvidenceIntegration(config)
|
|
3178
|
+
|
|
3179
|
+
scanner.sync_compliance_data()
|
|
3180
|
+
|
|
3181
|
+
logger.info("AWS S3 evidence sync completed successfully")
|
|
3182
|
+
|
|
3183
|
+
except Exception as e:
|
|
3184
|
+
logger.error(f"Error syncing AWS S3 evidence: {e}", exc_info=True)
|
|
3185
|
+
raise click.ClickException(str(e))
|
|
3186
|
+
|
|
3187
|
+
|
|
3188
|
+
@awsv2.command(name="sync_cloudtrail")
|
|
3189
|
+
@click.option("--region", default="us-east-1", help="AWS region to collect CloudTrail trails from")
|
|
3190
|
+
@click.option("--regscale-id", type=int, required=True, help="RegScale SSP plan ID")
|
|
3191
|
+
@click.option("--account-id", help="AWS account ID to filter resources")
|
|
3192
|
+
@click.option("--tags", help="Resource tags for filtering (format: key1=value1,key2=value2)")
|
|
3193
|
+
@click.option("--trail-name-filter", help="Filter trails by name prefix/pattern")
|
|
3194
|
+
@click.option("--create-evidence", is_flag=True, default=False, help="Create evidence records in RegScale")
|
|
3195
|
+
@click.option(
|
|
3196
|
+
"--create-ssp-attachment",
|
|
3197
|
+
is_flag=True,
|
|
3198
|
+
default=True,
|
|
3199
|
+
help="Create SSP attachment with evidence (default: True)",
|
|
3200
|
+
)
|
|
3201
|
+
@click.option(
|
|
3202
|
+
"--evidence-control-ids",
|
|
3203
|
+
help="Comma-separated control IDs to link evidence (e.g., AU-2,AU-3,AU-6)",
|
|
3204
|
+
)
|
|
3205
|
+
@click.option("--force-refresh", is_flag=True, default=False, help="Force refresh cached data")
|
|
3206
|
+
@click.option("--session-name", help="Custom session name for this operation")
|
|
3207
|
+
@click.option("--profile", help="AWS profile name to use for authentication")
|
|
3208
|
+
@click.option("--aws-access-key-id", help="AWS access key ID")
|
|
3209
|
+
@click.option("--aws-secret-access-key", help="AWS secret access key")
|
|
3210
|
+
@click.option("--aws-session-token", help="AWS session token")
|
|
3211
|
+
@click.pass_context
|
|
3212
|
+
def sync_cloudtrail(ctx, **kwargs):
|
|
3213
|
+
"""
|
|
3214
|
+
Sync AWS CloudTrail audit logging configurations to RegScale as compliance evidence.
|
|
3215
|
+
|
|
3216
|
+
Collects CloudTrail trail configurations including logging status, multi-region settings,
|
|
3217
|
+
log file validation, encryption, CloudWatch Logs integration, and SNS notifications for
|
|
3218
|
+
compliance assessment against NIST 800-53 R5 controls (AU-2, AU-3, AU-6, AU-9, AU-11, AU-12, SI-4).
|
|
3219
|
+
|
|
3220
|
+
Examples:
|
|
3221
|
+
|
|
3222
|
+
# Sync all CloudTrail trails in us-east-1
|
|
3223
|
+
regscale aws sync_cloudtrail --region us-east-1 --regscale-id 123
|
|
3224
|
+
|
|
3225
|
+
# Filter by trail name prefix
|
|
3226
|
+
regscale aws sync_cloudtrail --region us-east-1 --regscale-id 123 --trail-name-filter prod-
|
|
3227
|
+
|
|
3228
|
+
# Filter by tags
|
|
3229
|
+
regscale aws sync_cloudtrail --region us-east-1 --regscale-id 123 --tags Environment=Production
|
|
3230
|
+
|
|
3231
|
+
# Create evidence and link to controls
|
|
3232
|
+
regscale aws sync_cloudtrail --region us-east-1 --regscale-id 123 --create-evidence \\
|
|
3233
|
+
--evidence-control-ids AU-2,AU-3,AU-6,AU-9
|
|
3234
|
+
|
|
3235
|
+
# Use specific AWS profile
|
|
3236
|
+
regscale aws sync_cloudtrail --region us-west-2 --regscale-id 456 --profile production
|
|
3237
|
+
|
|
3238
|
+
# Force refresh cached data
|
|
3239
|
+
regscale aws sync_cloudtrail --region us-east-1 --regscale-id 123 --force-refresh
|
|
3240
|
+
"""
|
|
3241
|
+
from regscale.integrations.commercial.aws.cloudtrail_evidence import AWSCloudTrailEvidenceIntegration
|
|
3242
|
+
|
|
3243
|
+
try:
|
|
3244
|
+
# Extract parameters from kwargs
|
|
3245
|
+
region = kwargs["region"]
|
|
3246
|
+
regscale_id = kwargs["regscale_id"]
|
|
3247
|
+
account_id = kwargs.get("account_id")
|
|
3248
|
+
tags = kwargs.get("tags")
|
|
3249
|
+
trail_name_filter = kwargs.get("trail_name_filter")
|
|
3250
|
+
create_evidence = kwargs.get("create_evidence", False)
|
|
3251
|
+
create_ssp_attachment = kwargs.get("create_ssp_attachment", True)
|
|
3252
|
+
evidence_control_ids = kwargs.get("evidence_control_ids")
|
|
3253
|
+
force_refresh = kwargs.get("force_refresh", False)
|
|
3254
|
+
session_name = kwargs.get("session_name")
|
|
3255
|
+
profile = kwargs.get("profile")
|
|
3256
|
+
aws_access_key_id = kwargs.get("aws_access_key_id")
|
|
3257
|
+
aws_secret_access_key = kwargs.get("aws_secret_access_key")
|
|
3258
|
+
aws_session_token = kwargs.get("aws_session_token")
|
|
3259
|
+
|
|
3260
|
+
logger.info("Starting AWS CloudTrail audit logging sync to RegScale...")
|
|
3261
|
+
|
|
3262
|
+
resolved_profile, access_key, secret_key, session_token, resolved_region = resolve_aws_credentials(
|
|
3263
|
+
session_name, profile, aws_access_key_id, aws_secret_access_key, aws_session_token, region
|
|
3264
|
+
)
|
|
3265
|
+
|
|
3266
|
+
logger.info(
|
|
3267
|
+
f"Using AWS credentials - profile: {resolved_profile if resolved_profile else 'not set'}, "
|
|
3268
|
+
f"explicit credentials: {'yes' if access_key else 'no'}, region: {resolved_region}"
|
|
3269
|
+
)
|
|
3270
|
+
|
|
3271
|
+
parsed_tags = parse_tags(tags)
|
|
3272
|
+
|
|
3273
|
+
if account_id:
|
|
3274
|
+
logger.info(f"Filtering CloudTrail trails by account ID: {account_id}")
|
|
3275
|
+
|
|
3276
|
+
if parsed_tags:
|
|
3277
|
+
logger.info(f"Filtering CloudTrail trails by tags: {parsed_tags}")
|
|
3278
|
+
|
|
3279
|
+
if trail_name_filter:
|
|
3280
|
+
logger.info(f"Filtering CloudTrail trails by name pattern: {trail_name_filter}")
|
|
3281
|
+
|
|
3282
|
+
control_ids = None
|
|
3283
|
+
if evidence_control_ids:
|
|
3284
|
+
control_ids = [ctrl.strip() for ctrl in evidence_control_ids.split(",")]
|
|
3285
|
+
logger.info(f"Evidence collection requested for controls: {control_ids}")
|
|
3286
|
+
|
|
3287
|
+
if create_evidence or create_ssp_attachment:
|
|
3288
|
+
if create_ssp_attachment:
|
|
3289
|
+
logger.info(EVIDENCE_MODE_SSP_ATTACHMENTS)
|
|
3290
|
+
if create_evidence:
|
|
3291
|
+
logger.info(EVIDENCE_MODE_INDIVIDUAL_RECORDS)
|
|
3292
|
+
|
|
3293
|
+
from .cloudtrail_evidence import AWSCloudTrailEvidenceIntegration, CloudTrailEvidenceConfig
|
|
3294
|
+
|
|
3295
|
+
# Create configuration object
|
|
3296
|
+
config = CloudTrailEvidenceConfig(
|
|
3297
|
+
plan_id=regscale_id,
|
|
3298
|
+
region=resolved_region,
|
|
3299
|
+
account_id=account_id,
|
|
3300
|
+
tags=parsed_tags,
|
|
3301
|
+
trail_name_filter=trail_name_filter,
|
|
3302
|
+
create_evidence=create_evidence,
|
|
3303
|
+
create_ssp_attachment=create_ssp_attachment,
|
|
3304
|
+
evidence_control_ids=control_ids,
|
|
3305
|
+
force_refresh=force_refresh,
|
|
3306
|
+
aws_profile=resolved_profile,
|
|
3307
|
+
aws_access_key_id=access_key,
|
|
3308
|
+
aws_secret_access_key=secret_key,
|
|
3309
|
+
aws_session_token=session_token,
|
|
3310
|
+
)
|
|
3311
|
+
|
|
3312
|
+
scanner = AWSCloudTrailEvidenceIntegration(config)
|
|
3313
|
+
|
|
3314
|
+
scanner.sync_compliance_data()
|
|
3315
|
+
|
|
3316
|
+
logger.info("AWS CloudTrail evidence sync completed successfully")
|
|
3317
|
+
|
|
3318
|
+
except Exception as e:
|
|
3319
|
+
logger.error(f"Error syncing AWS CloudTrail evidence: {e}", exc_info=True)
|
|
3320
|
+
raise click.ClickException(str(e))
|
|
3321
|
+
|
|
3322
|
+
|
|
3323
|
+
@awsv2.command(name="sync_ssm")
|
|
3324
|
+
@click.option("--region", default="us-east-1", help="AWS region to sync from")
|
|
3325
|
+
@click.option("--regscale-id", type=int, required=True, help="RegScale SSP ID to attach evidence")
|
|
3326
|
+
@click.option("--account-id", help="Optional AWS account ID to filter resources")
|
|
3327
|
+
@click.option("--tags", help='Optional tags to filter resources (format: "Key1=Value1,Key2=Value2")')
|
|
3328
|
+
@click.option("--create-evidence", is_flag=True, default=False, help="Create evidence records (default: False)")
|
|
3329
|
+
@click.option("--create-ssp-attachment", is_flag=True, default=True, help="Create SSP attachments (default: True)")
|
|
3330
|
+
@click.option("--evidence-control-ids", help='Control IDs for evidence (comma-separated, e.g., "CM-2,CM-6,SI-2")')
|
|
3331
|
+
@click.option("--force-refresh", is_flag=True, default=False, help="Force cache refresh")
|
|
3332
|
+
@click.option("--session-name", help="AWS session name for role assumption")
|
|
3333
|
+
@click.option("--profile", help="AWS profile name to use")
|
|
3334
|
+
@click.option("--aws-access-key-id", help="AWS access key ID")
|
|
3335
|
+
@click.option("--aws-secret-access-key", help="AWS secret access key")
|
|
3336
|
+
@click.option("--aws-session-token", help="AWS session token")
|
|
3337
|
+
@click.pass_context
|
|
3338
|
+
def sync_ssm(ctx, **kwargs):
|
|
3339
|
+
"""Sync AWS Systems Manager configurations to RegScale as compliance evidence.
|
|
3340
|
+
|
|
3341
|
+
This command collects SSM configuration data including managed instances, patch baselines,
|
|
3342
|
+
parameters, documents, maintenance windows, and compliance status for NIST 800-53 R5
|
|
3343
|
+
controls (CM-2, CM-6, SI-2, CM-3, CM-8).
|
|
3344
|
+
|
|
3345
|
+
Examples:
|
|
3346
|
+
# Sync SSM evidence with default settings
|
|
3347
|
+
regscale commercial aws sync_ssm --regscale-id 123
|
|
3348
|
+
|
|
3349
|
+
# Sync with specific AWS profile
|
|
3350
|
+
regscale commercial aws sync_ssm --regscale-id 123 --profile prod-account
|
|
3351
|
+
|
|
3352
|
+
# Force refresh cache and filter by tags
|
|
3353
|
+
regscale commercial aws sync_ssm --regscale-id 123 --force-refresh --tags "Environment=Production"
|
|
3354
|
+
|
|
3355
|
+
# Sync for specific account
|
|
3356
|
+
regscale commercial aws sync_ssm --regscale-id 123 --account-id 123456789012
|
|
3357
|
+
"""
|
|
3358
|
+
from regscale.integrations.commercial.aws.ssm_evidence import AWSSSMEvidenceIntegration
|
|
3359
|
+
|
|
3360
|
+
try:
|
|
3361
|
+
# Extract parameters from kwargs
|
|
3362
|
+
region = kwargs["region"]
|
|
3363
|
+
regscale_id = kwargs["regscale_id"]
|
|
3364
|
+
account_id = kwargs.get("account_id")
|
|
3365
|
+
tags = kwargs.get("tags")
|
|
3366
|
+
create_evidence = kwargs.get("create_evidence", False)
|
|
3367
|
+
create_ssp_attachment = kwargs.get("create_ssp_attachment", True)
|
|
3368
|
+
evidence_control_ids = kwargs.get("evidence_control_ids")
|
|
3369
|
+
force_refresh = kwargs.get("force_refresh", False)
|
|
3370
|
+
session_name = kwargs.get("session_name")
|
|
3371
|
+
profile = kwargs.get("profile")
|
|
3372
|
+
aws_access_key_id = kwargs.get("aws_access_key_id")
|
|
3373
|
+
aws_secret_access_key = kwargs.get("aws_secret_access_key")
|
|
3374
|
+
aws_session_token = kwargs.get("aws_session_token")
|
|
3375
|
+
|
|
3376
|
+
# Resolve AWS credentials
|
|
3377
|
+
resolved_profile, access_key, secret_key, session_token, resolved_region = resolve_aws_credentials(
|
|
3378
|
+
session_name, profile, aws_access_key_id, aws_secret_access_key, aws_session_token, region
|
|
3379
|
+
)
|
|
3380
|
+
|
|
3381
|
+
# Parse tags if provided
|
|
3382
|
+
parsed_tags = parse_tags(tags)
|
|
3383
|
+
|
|
3384
|
+
# Parse control IDs if provided
|
|
3385
|
+
control_ids = [ctrl.strip() for ctrl in evidence_control_ids.split(",")] if evidence_control_ids else None
|
|
3386
|
+
|
|
3387
|
+
logger.info(f"Starting AWS Systems Manager evidence sync for region: {resolved_region}")
|
|
3388
|
+
|
|
3389
|
+
from .ssm_evidence import AWSSSMEvidenceIntegration, SSMEvidenceConfig
|
|
3390
|
+
|
|
3391
|
+
# Create configuration object
|
|
3392
|
+
config = SSMEvidenceConfig(
|
|
3393
|
+
plan_id=regscale_id,
|
|
3394
|
+
region=resolved_region,
|
|
3395
|
+
account_id=account_id,
|
|
3396
|
+
tags=parsed_tags,
|
|
3397
|
+
create_evidence=create_evidence,
|
|
3398
|
+
create_ssp_attachment=create_ssp_attachment,
|
|
3399
|
+
evidence_control_ids=control_ids,
|
|
3400
|
+
force_refresh=force_refresh,
|
|
3401
|
+
aws_profile=resolved_profile,
|
|
3402
|
+
aws_access_key_id=access_key,
|
|
3403
|
+
aws_secret_access_key=secret_key,
|
|
3404
|
+
aws_session_token=session_token,
|
|
3405
|
+
)
|
|
3406
|
+
|
|
3407
|
+
scanner = AWSSSMEvidenceIntegration(config)
|
|
3408
|
+
|
|
3409
|
+
scanner.sync_compliance_data()
|
|
3410
|
+
|
|
3411
|
+
logger.info("AWS Systems Manager evidence sync completed successfully")
|
|
3412
|
+
|
|
3413
|
+
except Exception as e:
|
|
3414
|
+
logger.error(f"Error syncing AWS Systems Manager evidence: {e}", exc_info=True)
|
|
3415
|
+
raise click.ClickException(str(e))
|
|
3416
|
+
|
|
3417
|
+
|
|
3418
|
+
@awsv2.command(name="sync_cloudwatch")
|
|
3419
|
+
@click.option("--region", default="us-east-1", help="AWS region to collect CloudWatch Logs from")
|
|
3420
|
+
@click.option("--regscale-id", type=int, required=True, help="RegScale SSP plan ID")
|
|
3421
|
+
@click.option("--account-id", help="AWS account ID to filter resources")
|
|
3422
|
+
@click.option("--tags", help="Resource tags for filtering (format: key1=value1,key2=value2)")
|
|
3423
|
+
@click.option("--log-group-prefix", help="Filter log groups by name prefix")
|
|
3424
|
+
@click.option("--create-evidence", is_flag=True, default=False, help="Create evidence records in RegScale")
|
|
3425
|
+
@click.option(
|
|
3426
|
+
"--create-ssp-attachment",
|
|
3427
|
+
is_flag=True,
|
|
3428
|
+
default=True,
|
|
3429
|
+
help="Create SSP attachment with evidence (default: True)",
|
|
3430
|
+
)
|
|
3431
|
+
@click.option(
|
|
3432
|
+
"--evidence-control-ids",
|
|
3433
|
+
help="Comma-separated control IDs to link evidence (e.g., AU-2,AU-3,AU-6)",
|
|
3434
|
+
)
|
|
3435
|
+
@click.option("--force-refresh", is_flag=True, default=False, help="Force refresh cached data")
|
|
3436
|
+
@click.option("--session-name", help="Custom session name for this operation")
|
|
3437
|
+
@click.option("--profile", help="AWS profile name to use for authentication")
|
|
3438
|
+
@click.option("--aws-access-key-id", help="AWS access key ID")
|
|
3439
|
+
@click.option("--aws-secret-access-key", help="AWS secret access key")
|
|
3440
|
+
@click.option("--aws-session-token", help="AWS session token")
|
|
3441
|
+
@click.pass_context
|
|
3442
|
+
def sync_cloudwatch(ctx, **kwargs):
|
|
3443
|
+
"""
|
|
3444
|
+
Sync AWS CloudWatch Logs configurations to RegScale as compliance evidence.
|
|
3445
|
+
|
|
3446
|
+
Collects CloudWatch log group configurations including retention policies, encryption status,
|
|
3447
|
+
metric filters, subscription filters, and storage metrics for compliance assessment against
|
|
3448
|
+
NIST 800-53 R5 controls (AU-2, AU-3, AU-6, AU-9, AU-11, AU-12, SI-4).
|
|
3449
|
+
|
|
3450
|
+
Examples:
|
|
3451
|
+
|
|
3452
|
+
# Sync all CloudWatch log groups in us-east-1
|
|
3453
|
+
regscale aws sync_cloudwatch --region us-east-1 --regscale-id 123
|
|
3454
|
+
|
|
3455
|
+
# Filter by log group name prefix
|
|
3456
|
+
regscale aws sync_cloudwatch --region us-east-1 --regscale-id 123 --log-group-prefix /aws/lambda/
|
|
3457
|
+
|
|
3458
|
+
# Filter by tags
|
|
3459
|
+
regscale aws sync_cloudwatch --region us-east-1 --regscale-id 123 --tags Environment=Production
|
|
3460
|
+
|
|
3461
|
+
# Create evidence and link to controls
|
|
3462
|
+
regscale aws sync_cloudwatch --region us-east-1 --regscale-id 123 --create-evidence --evidence-control-ids AU-2,AU-3,AU-6,AU-9
|
|
3463
|
+
|
|
3464
|
+
# Use specific AWS profile
|
|
3465
|
+
regscale aws sync_cloudwatch --region us-west-2 --regscale-id 456 --profile production
|
|
3466
|
+
|
|
3467
|
+
# Force refresh cached data
|
|
3468
|
+
regscale aws sync_cloudwatch --region us-east-1 --regscale-id 123 --force-refresh
|
|
3469
|
+
"""
|
|
3470
|
+
from regscale.integrations.commercial.aws.cloudwatch_evidence import AWSCloudWatchEvidenceIntegration
|
|
3471
|
+
|
|
3472
|
+
try:
|
|
3473
|
+
# Extract parameters from kwargs
|
|
3474
|
+
region = kwargs["region"]
|
|
3475
|
+
regscale_id = kwargs["regscale_id"]
|
|
3476
|
+
account_id = kwargs.get("account_id")
|
|
3477
|
+
tags = kwargs.get("tags")
|
|
3478
|
+
log_group_prefix = kwargs.get("log_group_prefix")
|
|
3479
|
+
create_evidence = kwargs.get("create_evidence", False)
|
|
3480
|
+
create_ssp_attachment = kwargs.get("create_ssp_attachment", True)
|
|
3481
|
+
evidence_control_ids = kwargs.get("evidence_control_ids")
|
|
3482
|
+
force_refresh = kwargs.get("force_refresh", False)
|
|
3483
|
+
session_name = kwargs.get("session_name")
|
|
3484
|
+
profile = kwargs.get("profile")
|
|
3485
|
+
aws_access_key_id = kwargs.get("aws_access_key_id")
|
|
3486
|
+
aws_secret_access_key = kwargs.get("aws_secret_access_key")
|
|
3487
|
+
aws_session_token = kwargs.get("aws_session_token")
|
|
3488
|
+
|
|
3489
|
+
logger.info("Starting AWS CloudWatch Logs sync to RegScale...")
|
|
3490
|
+
|
|
3491
|
+
resolved_profile, access_key, secret_key, session_token, resolved_region = resolve_aws_credentials(
|
|
3492
|
+
session_name, profile, aws_access_key_id, aws_secret_access_key, aws_session_token, region
|
|
3493
|
+
)
|
|
3494
|
+
|
|
3495
|
+
logger.info(
|
|
3496
|
+
f"Using AWS credentials - profile: {resolved_profile if resolved_profile else 'not set'}, "
|
|
3497
|
+
f"explicit credentials: {'yes' if access_key else 'no'}, region: {resolved_region}"
|
|
3498
|
+
)
|
|
3499
|
+
|
|
3500
|
+
parsed_tags = parse_tags(tags)
|
|
3501
|
+
|
|
3502
|
+
if account_id:
|
|
3503
|
+
logger.info(f"Filtering CloudWatch log groups by account ID: {account_id}")
|
|
3504
|
+
|
|
3505
|
+
if parsed_tags:
|
|
3506
|
+
logger.info(f"Filtering CloudWatch log groups by tags: {parsed_tags}")
|
|
3507
|
+
|
|
3508
|
+
if log_group_prefix:
|
|
3509
|
+
logger.info(f"Filtering CloudWatch log groups by name prefix: {log_group_prefix}")
|
|
3510
|
+
|
|
3511
|
+
control_ids = None
|
|
3512
|
+
if evidence_control_ids:
|
|
3513
|
+
control_ids = [ctrl.strip() for ctrl in evidence_control_ids.split(",")]
|
|
3514
|
+
logger.info(f"Evidence collection requested for controls: {control_ids}")
|
|
3515
|
+
|
|
3516
|
+
if create_evidence or create_ssp_attachment:
|
|
3517
|
+
if create_ssp_attachment:
|
|
3518
|
+
logger.info(EVIDENCE_MODE_SSP_ATTACHMENTS)
|
|
3519
|
+
if create_evidence:
|
|
3520
|
+
logger.info(EVIDENCE_MODE_INDIVIDUAL_RECORDS)
|
|
3521
|
+
|
|
3522
|
+
from .cloudwatch_evidence import AWSCloudWatchEvidenceIntegration, CloudWatchEvidenceConfig
|
|
3523
|
+
|
|
3524
|
+
# Create configuration object
|
|
3525
|
+
config = CloudWatchEvidenceConfig(
|
|
3526
|
+
plan_id=regscale_id,
|
|
3527
|
+
region=resolved_region,
|
|
3528
|
+
account_id=account_id,
|
|
3529
|
+
tags=parsed_tags,
|
|
3530
|
+
log_group_prefix=log_group_prefix,
|
|
3531
|
+
create_evidence=create_evidence,
|
|
3532
|
+
create_ssp_attachment=create_ssp_attachment,
|
|
3533
|
+
evidence_control_ids=control_ids,
|
|
3534
|
+
force_refresh=force_refresh,
|
|
3535
|
+
aws_profile=resolved_profile,
|
|
3536
|
+
aws_access_key_id=access_key,
|
|
3537
|
+
aws_secret_access_key=secret_key,
|
|
3538
|
+
aws_session_token=session_token,
|
|
3539
|
+
)
|
|
3540
|
+
|
|
3541
|
+
scanner = AWSCloudWatchEvidenceIntegration(config)
|
|
3542
|
+
|
|
3543
|
+
scanner.sync_compliance_data()
|
|
3544
|
+
|
|
3545
|
+
logger.info("AWS CloudWatch Logs evidence sync completed successfully")
|
|
3546
|
+
|
|
3547
|
+
except Exception as e:
|
|
3548
|
+
logger.error(f"Error syncing AWS CloudWatch Logs evidence: {e}", exc_info=True)
|
|
496
3549
|
raise click.ClickException(str(e))
|