regscale-cli 6.21.2.0__py3-none-any.whl → 6.28.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- regscale/_version.py +1 -1
- regscale/airflow/hierarchy.py +2 -2
- regscale/core/app/api.py +5 -2
- regscale/core/app/application.py +36 -6
- regscale/core/app/internal/control_editor.py +73 -21
- regscale/core/app/internal/evidence.py +727 -204
- regscale/core/app/internal/login.py +4 -2
- regscale/core/app/internal/model_editor.py +219 -64
- regscale/core/app/utils/app_utils.py +86 -12
- regscale/core/app/utils/catalog_utils/common.py +1 -1
- regscale/core/login.py +21 -4
- regscale/core/utils/async_graphql_client.py +363 -0
- regscale/core/utils/date.py +77 -1
- regscale/dev/cli.py +26 -0
- regscale/dev/code_gen.py +109 -24
- regscale/dev/version.py +72 -0
- regscale/integrations/commercial/__init__.py +30 -2
- regscale/integrations/commercial/aws/audit_manager_compliance.py +3908 -0
- regscale/integrations/commercial/aws/cli.py +3107 -54
- regscale/integrations/commercial/aws/cloudtrail_control_mappings.py +333 -0
- regscale/integrations/commercial/aws/cloudtrail_evidence.py +501 -0
- regscale/integrations/commercial/aws/cloudwatch_control_mappings.py +357 -0
- regscale/integrations/commercial/aws/cloudwatch_evidence.py +490 -0
- regscale/integrations/commercial/{amazon → aws}/common.py +71 -19
- regscale/integrations/commercial/aws/config_compliance.py +914 -0
- regscale/integrations/commercial/aws/conformance_pack_mappings.py +198 -0
- regscale/integrations/commercial/aws/control_compliance_analyzer.py +439 -0
- regscale/integrations/commercial/aws/evidence_generator.py +283 -0
- regscale/integrations/commercial/aws/guardduty_control_mappings.py +340 -0
- regscale/integrations/commercial/aws/guardduty_evidence.py +1053 -0
- regscale/integrations/commercial/aws/iam_control_mappings.py +368 -0
- regscale/integrations/commercial/aws/iam_evidence.py +574 -0
- regscale/integrations/commercial/aws/inventory/__init__.py +338 -22
- regscale/integrations/commercial/aws/inventory/base.py +107 -5
- regscale/integrations/commercial/aws/inventory/resources/analytics.py +390 -0
- regscale/integrations/commercial/aws/inventory/resources/applications.py +234 -0
- regscale/integrations/commercial/aws/inventory/resources/audit_manager.py +513 -0
- regscale/integrations/commercial/aws/inventory/resources/cloudtrail.py +315 -0
- regscale/integrations/commercial/aws/inventory/resources/cloudtrail_logs_metadata.py +476 -0
- regscale/integrations/commercial/aws/inventory/resources/cloudwatch.py +191 -0
- regscale/integrations/commercial/aws/inventory/resources/compute.py +328 -9
- regscale/integrations/commercial/aws/inventory/resources/config.py +464 -0
- regscale/integrations/commercial/aws/inventory/resources/containers.py +74 -9
- regscale/integrations/commercial/aws/inventory/resources/database.py +481 -31
- regscale/integrations/commercial/aws/inventory/resources/developer_tools.py +253 -0
- regscale/integrations/commercial/aws/inventory/resources/guardduty.py +286 -0
- regscale/integrations/commercial/aws/inventory/resources/iam.py +470 -0
- regscale/integrations/commercial/aws/inventory/resources/inspector.py +476 -0
- regscale/integrations/commercial/aws/inventory/resources/integration.py +175 -61
- regscale/integrations/commercial/aws/inventory/resources/kms.py +447 -0
- regscale/integrations/commercial/aws/inventory/resources/machine_learning.py +358 -0
- regscale/integrations/commercial/aws/inventory/resources/networking.py +390 -67
- regscale/integrations/commercial/aws/inventory/resources/s3.py +394 -0
- regscale/integrations/commercial/aws/inventory/resources/security.py +268 -72
- regscale/integrations/commercial/aws/inventory/resources/securityhub.py +473 -0
- regscale/integrations/commercial/aws/inventory/resources/storage.py +288 -29
- regscale/integrations/commercial/aws/inventory/resources/systems_manager.py +657 -0
- regscale/integrations/commercial/aws/inventory/resources/vpc.py +655 -0
- regscale/integrations/commercial/aws/kms_control_mappings.py +288 -0
- regscale/integrations/commercial/aws/kms_evidence.py +879 -0
- regscale/integrations/commercial/aws/ocsf/__init__.py +7 -0
- regscale/integrations/commercial/aws/ocsf/constants.py +115 -0
- regscale/integrations/commercial/aws/ocsf/mapper.py +435 -0
- regscale/integrations/commercial/aws/org_control_mappings.py +286 -0
- regscale/integrations/commercial/aws/org_evidence.py +666 -0
- regscale/integrations/commercial/aws/s3_control_mappings.py +356 -0
- regscale/integrations/commercial/aws/s3_evidence.py +632 -0
- regscale/integrations/commercial/aws/scanner.py +1072 -205
- regscale/integrations/commercial/aws/security_hub.py +319 -0
- regscale/integrations/commercial/aws/session_manager.py +282 -0
- regscale/integrations/commercial/aws/ssm_control_mappings.py +291 -0
- regscale/integrations/commercial/aws/ssm_evidence.py +492 -0
- regscale/integrations/commercial/jira.py +489 -153
- regscale/integrations/commercial/microsoft_defender/defender.py +326 -5
- regscale/integrations/commercial/microsoft_defender/defender_api.py +348 -14
- regscale/integrations/commercial/microsoft_defender/defender_constants.py +157 -0
- regscale/integrations/commercial/qualys/__init__.py +167 -68
- regscale/integrations/commercial/qualys/scanner.py +305 -39
- regscale/integrations/commercial/sarif/sairf_importer.py +432 -0
- regscale/integrations/commercial/sarif/sarif_converter.py +67 -0
- regscale/integrations/commercial/sicura/api.py +79 -42
- regscale/integrations/commercial/sicura/commands.py +8 -2
- regscale/integrations/commercial/sicura/scanner.py +83 -44
- regscale/integrations/commercial/stigv2/ckl_parser.py +5 -5
- regscale/integrations/commercial/synqly/assets.py +133 -16
- regscale/integrations/commercial/synqly/edr.py +2 -8
- regscale/integrations/commercial/synqly/query_builder.py +536 -0
- regscale/integrations/commercial/synqly/ticketing.py +27 -0
- regscale/integrations/commercial/synqly/vulnerabilities.py +165 -28
- regscale/integrations/commercial/tenablev2/cis_parsers.py +453 -0
- regscale/integrations/commercial/tenablev2/cis_scanner.py +447 -0
- regscale/integrations/commercial/tenablev2/commands.py +146 -5
- regscale/integrations/commercial/tenablev2/scanner.py +1 -3
- regscale/integrations/commercial/tenablev2/stig_parsers.py +113 -57
- regscale/integrations/commercial/wizv2/WizDataMixin.py +1 -1
- regscale/integrations/commercial/wizv2/click.py +191 -76
- regscale/integrations/commercial/wizv2/compliance/__init__.py +15 -0
- regscale/integrations/commercial/wizv2/{policy_compliance_helpers.py → compliance/helpers.py} +78 -60
- regscale/integrations/commercial/wizv2/compliance_report.py +1592 -0
- regscale/integrations/commercial/wizv2/core/__init__.py +133 -0
- regscale/integrations/commercial/wizv2/{async_client.py → core/client.py} +7 -3
- regscale/integrations/commercial/wizv2/{constants.py → core/constants.py} +92 -89
- regscale/integrations/commercial/wizv2/core/file_operations.py +237 -0
- regscale/integrations/commercial/wizv2/fetchers/__init__.py +11 -0
- regscale/integrations/commercial/wizv2/{data_fetcher.py → fetchers/policy_assessment.py} +66 -9
- regscale/integrations/commercial/wizv2/file_cleanup.py +104 -0
- regscale/integrations/commercial/wizv2/issue.py +776 -28
- regscale/integrations/commercial/wizv2/models/__init__.py +0 -0
- regscale/integrations/commercial/wizv2/parsers/__init__.py +34 -0
- regscale/integrations/commercial/wizv2/{parsers.py → parsers/main.py} +1 -1
- regscale/integrations/commercial/wizv2/processors/__init__.py +11 -0
- regscale/integrations/commercial/wizv2/{finding_processor.py → processors/finding.py} +1 -1
- regscale/integrations/commercial/wizv2/reports.py +243 -0
- regscale/integrations/commercial/wizv2/sbom.py +1 -1
- regscale/integrations/commercial/wizv2/scanner.py +1031 -441
- regscale/integrations/commercial/wizv2/utils/__init__.py +48 -0
- regscale/integrations/commercial/wizv2/{utils.py → utils/main.py} +116 -61
- regscale/integrations/commercial/wizv2/variables.py +89 -3
- regscale/integrations/compliance_integration.py +1036 -151
- regscale/integrations/control_matcher.py +432 -0
- regscale/integrations/due_date_handler.py +333 -0
- regscale/integrations/milestone_manager.py +291 -0
- regscale/integrations/public/__init__.py +14 -0
- regscale/integrations/public/cci_importer.py +834 -0
- regscale/integrations/public/csam/__init__.py +0 -0
- regscale/integrations/public/csam/csam.py +938 -0
- regscale/integrations/public/csam/csam_agency_defined.py +179 -0
- regscale/integrations/public/csam/csam_common.py +154 -0
- regscale/integrations/public/csam/csam_controls.py +432 -0
- regscale/integrations/public/csam/csam_poam.py +124 -0
- regscale/integrations/public/fedramp/click.py +77 -6
- regscale/integrations/public/fedramp/docx_parser.py +10 -1
- regscale/integrations/public/fedramp/fedramp_cis_crm.py +675 -289
- regscale/integrations/public/fedramp/fedramp_five.py +1 -1
- regscale/integrations/public/fedramp/poam/scanner.py +75 -7
- regscale/integrations/public/fedramp/poam_export_v5.py +888 -0
- regscale/integrations/scanner_integration.py +1961 -430
- regscale/models/integration_models/CCI_List.xml +1 -0
- regscale/models/integration_models/aqua.py +2 -2
- regscale/models/integration_models/cisa_kev_data.json +805 -11
- regscale/models/integration_models/flat_file_importer/__init__.py +5 -8
- regscale/models/integration_models/nexpose.py +36 -10
- regscale/models/integration_models/qualys.py +3 -4
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +87 -18
- regscale/models/integration_models/synqly_models/filter_parser.py +332 -0
- regscale/models/integration_models/synqly_models/ocsf_mapper.py +124 -25
- regscale/models/integration_models/synqly_models/synqly_model.py +89 -16
- regscale/models/locking.py +12 -8
- regscale/models/platform.py +4 -2
- regscale/models/regscale_models/__init__.py +7 -0
- regscale/models/regscale_models/assessment.py +2 -1
- regscale/models/regscale_models/catalog.py +1 -1
- regscale/models/regscale_models/compliance_settings.py +251 -1
- regscale/models/regscale_models/component.py +1 -0
- regscale/models/regscale_models/control_implementation.py +236 -41
- regscale/models/regscale_models/control_objective.py +74 -5
- regscale/models/regscale_models/file.py +2 -0
- regscale/models/regscale_models/form_field_value.py +5 -3
- regscale/models/regscale_models/inheritance.py +44 -0
- regscale/models/regscale_models/issue.py +301 -102
- regscale/models/regscale_models/milestone.py +33 -14
- regscale/models/regscale_models/organization.py +3 -0
- regscale/models/regscale_models/regscale_model.py +310 -73
- regscale/models/regscale_models/security_plan.py +4 -2
- regscale/models/regscale_models/vulnerability.py +3 -3
- regscale/regscale.py +25 -4
- regscale/templates/__init__.py +0 -0
- regscale/utils/threading/threadhandler.py +20 -15
- regscale/validation/record.py +23 -1
- {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/METADATA +17 -33
- {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/RECORD +310 -111
- tests/core/__init__.py +0 -0
- tests/core/utils/__init__.py +0 -0
- tests/core/utils/test_async_graphql_client.py +472 -0
- tests/fixtures/test_fixture.py +13 -8
- tests/regscale/core/test_login.py +171 -4
- tests/regscale/integrations/commercial/__init__.py +0 -0
- tests/regscale/integrations/commercial/aws/__init__.py +0 -0
- tests/regscale/integrations/commercial/aws/test_audit_manager_compliance.py +1304 -0
- tests/regscale/integrations/commercial/aws/test_audit_manager_evidence_aggregation.py +341 -0
- tests/regscale/integrations/commercial/aws/test_aws_analytics_collector.py +260 -0
- tests/regscale/integrations/commercial/aws/test_aws_applications_collector.py +242 -0
- tests/regscale/integrations/commercial/aws/test_aws_audit_manager_collector.py +1155 -0
- tests/regscale/integrations/commercial/aws/test_aws_cloudtrail_collector.py +534 -0
- tests/regscale/integrations/commercial/aws/test_aws_config_collector.py +400 -0
- tests/regscale/integrations/commercial/aws/test_aws_developer_tools_collector.py +203 -0
- tests/regscale/integrations/commercial/aws/test_aws_guardduty_collector.py +315 -0
- tests/regscale/integrations/commercial/aws/test_aws_iam_collector.py +458 -0
- tests/regscale/integrations/commercial/aws/test_aws_inspector_collector.py +353 -0
- tests/regscale/integrations/commercial/aws/test_aws_inventory_integration.py +530 -0
- tests/regscale/integrations/commercial/aws/test_aws_kms_collector.py +919 -0
- tests/regscale/integrations/commercial/aws/test_aws_machine_learning_collector.py +237 -0
- tests/regscale/integrations/commercial/aws/test_aws_s3_collector.py +722 -0
- tests/regscale/integrations/commercial/aws/test_aws_scanner_integration.py +722 -0
- tests/regscale/integrations/commercial/aws/test_aws_securityhub_collector.py +792 -0
- tests/regscale/integrations/commercial/aws/test_aws_systems_manager_collector.py +918 -0
- tests/regscale/integrations/commercial/aws/test_aws_vpc_collector.py +996 -0
- tests/regscale/integrations/commercial/aws/test_cli_evidence.py +431 -0
- tests/regscale/integrations/commercial/aws/test_cloudtrail_control_mappings.py +452 -0
- tests/regscale/integrations/commercial/aws/test_cloudtrail_evidence.py +788 -0
- tests/regscale/integrations/commercial/aws/test_config_compliance.py +298 -0
- tests/regscale/integrations/commercial/aws/test_conformance_pack_mappings.py +200 -0
- tests/regscale/integrations/commercial/aws/test_control_compliance_analyzer.py +375 -0
- tests/regscale/integrations/commercial/aws/test_datetime_parsing.py +223 -0
- tests/regscale/integrations/commercial/aws/test_evidence_generator.py +386 -0
- tests/regscale/integrations/commercial/aws/test_guardduty_control_mappings.py +564 -0
- tests/regscale/integrations/commercial/aws/test_guardduty_evidence.py +1041 -0
- tests/regscale/integrations/commercial/aws/test_iam_control_mappings.py +718 -0
- tests/regscale/integrations/commercial/aws/test_iam_evidence.py +1375 -0
- tests/regscale/integrations/commercial/aws/test_kms_control_mappings.py +656 -0
- tests/regscale/integrations/commercial/aws/test_kms_evidence.py +1163 -0
- tests/regscale/integrations/commercial/aws/test_ocsf_mapper.py +370 -0
- tests/regscale/integrations/commercial/aws/test_org_control_mappings.py +546 -0
- tests/regscale/integrations/commercial/aws/test_org_evidence.py +1240 -0
- tests/regscale/integrations/commercial/aws/test_s3_control_mappings.py +672 -0
- tests/regscale/integrations/commercial/aws/test_s3_evidence.py +987 -0
- tests/regscale/integrations/commercial/aws/test_scanner_evidence.py +373 -0
- tests/regscale/integrations/commercial/aws/test_security_hub_config_filtering.py +539 -0
- tests/regscale/integrations/commercial/aws/test_session_manager.py +516 -0
- tests/regscale/integrations/commercial/aws/test_ssm_control_mappings.py +588 -0
- tests/regscale/integrations/commercial/aws/test_ssm_evidence.py +735 -0
- tests/regscale/integrations/commercial/conftest.py +28 -0
- tests/regscale/integrations/commercial/microsoft_defender/__init__.py +1 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender.py +1517 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_api.py +1748 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_constants.py +327 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_scanner.py +487 -0
- tests/regscale/integrations/commercial/test_aws.py +3742 -0
- tests/regscale/integrations/commercial/test_burp.py +48 -0
- tests/regscale/integrations/commercial/test_crowdstrike.py +49 -0
- tests/regscale/integrations/commercial/test_dependabot.py +341 -0
- tests/regscale/integrations/commercial/test_gcp.py +1543 -0
- tests/regscale/integrations/commercial/test_gitlab.py +549 -0
- tests/regscale/integrations/commercial/test_ip_mac_address_length.py +84 -0
- tests/regscale/integrations/commercial/test_jira.py +2204 -0
- tests/regscale/integrations/commercial/test_npm_audit.py +42 -0
- tests/regscale/integrations/commercial/test_okta.py +1228 -0
- tests/regscale/integrations/commercial/test_sarif_converter.py +251 -0
- tests/regscale/integrations/commercial/test_sicura.py +349 -0
- tests/regscale/integrations/commercial/test_snow.py +423 -0
- tests/regscale/integrations/commercial/test_sonarcloud.py +394 -0
- tests/regscale/integrations/commercial/test_sqlserver.py +186 -0
- tests/regscale/integrations/commercial/test_stig.py +33 -0
- tests/regscale/integrations/commercial/test_stig_mapper.py +153 -0
- tests/regscale/integrations/commercial/test_stigv2.py +406 -0
- tests/regscale/integrations/commercial/test_wiz.py +1365 -0
- tests/regscale/integrations/commercial/test_wiz_inventory.py +256 -0
- tests/regscale/integrations/commercial/wizv2/__init__.py +339 -0
- tests/regscale/integrations/commercial/wizv2/compliance/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/compliance/test_helpers.py +903 -0
- tests/regscale/integrations/commercial/wizv2/core/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/core/test_auth.py +701 -0
- tests/regscale/integrations/commercial/wizv2/core/test_client.py +1037 -0
- tests/regscale/integrations/commercial/wizv2/core/test_file_operations.py +989 -0
- tests/regscale/integrations/commercial/wizv2/fetchers/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/fetchers/test_policy_assessment.py +805 -0
- tests/regscale/integrations/commercial/wizv2/parsers/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/parsers/test_main.py +1153 -0
- tests/regscale/integrations/commercial/wizv2/processors/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/processors/test_finding.py +671 -0
- tests/regscale/integrations/commercial/wizv2/test_WizDataMixin.py +537 -0
- tests/regscale/integrations/commercial/wizv2/test_click_comprehensive.py +851 -0
- tests/regscale/integrations/commercial/wizv2/test_compliance_report_comprehensive.py +910 -0
- tests/regscale/integrations/commercial/wizv2/test_compliance_report_normalization.py +138 -0
- tests/regscale/integrations/commercial/wizv2/test_file_cleanup.py +283 -0
- tests/regscale/integrations/commercial/wizv2/test_file_operations.py +260 -0
- tests/regscale/integrations/commercial/wizv2/test_issue.py +343 -0
- tests/regscale/integrations/commercial/wizv2/test_issue_comprehensive.py +1203 -0
- tests/regscale/integrations/commercial/wizv2/test_reports.py +497 -0
- tests/regscale/integrations/commercial/wizv2/test_sbom.py +643 -0
- tests/regscale/integrations/commercial/wizv2/test_scanner_comprehensive.py +805 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_click_client_id.py +165 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_report.py +1394 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_unit.py +341 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_control_normalization.py +138 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_findings_comprehensive.py +364 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_inventory_comprehensive.py +644 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_status_mapping.py +149 -0
- tests/regscale/integrations/commercial/wizv2/test_wizv2.py +1218 -0
- tests/regscale/integrations/commercial/wizv2/test_wizv2_utils.py +519 -0
- tests/regscale/integrations/commercial/wizv2/utils/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/utils/test_main.py +1523 -0
- tests/regscale/integrations/public/__init__.py +0 -0
- tests/regscale/integrations/public/fedramp/__init__.py +1 -0
- tests/regscale/integrations/public/fedramp/test_gen_asset_list.py +150 -0
- tests/regscale/integrations/public/fedramp/test_poam_export_v5.py +1293 -0
- tests/regscale/integrations/public/test_alienvault.py +220 -0
- tests/regscale/integrations/public/test_cci.py +1053 -0
- tests/regscale/integrations/public/test_cisa.py +1021 -0
- tests/regscale/integrations/public/test_emass.py +518 -0
- tests/regscale/integrations/public/test_fedramp.py +1152 -0
- tests/regscale/integrations/public/test_fedramp_cis_crm.py +3661 -0
- tests/regscale/integrations/public/test_file_uploads.py +506 -0
- tests/regscale/integrations/public/test_oscal.py +453 -0
- tests/regscale/integrations/test_compliance_status_mapping.py +406 -0
- tests/regscale/integrations/test_control_matcher.py +1421 -0
- tests/regscale/integrations/test_control_matching.py +155 -0
- tests/regscale/integrations/test_milestone_manager.py +408 -0
- tests/regscale/models/test_control_implementation.py +118 -3
- tests/regscale/models/test_form_field_value_integration.py +304 -0
- tests/regscale/models/test_issue.py +378 -1
- tests/regscale/models/test_module_integration.py +582 -0
- tests/regscale/models/test_tenable_integrations.py +811 -105
- regscale/integrations/commercial/wizv2/policy_compliance.py +0 -3057
- regscale/integrations/public/fedramp/mappings/fedramp_r4_parts.json +0 -7388
- regscale/integrations/public/fedramp/mappings/fedramp_r5_parts.json +0 -9605
- regscale/integrations/public/fedramp/parts_mapper.py +0 -107
- /regscale/integrations/commercial/{amazon → sarif}/__init__.py +0 -0
- /regscale/integrations/commercial/wizv2/{wiz_auth.py → core/auth.py} +0 -0
- {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/LICENSE +0 -0
- {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/WHEEL +0 -0
- {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.21.2.0.dist-info → regscale_cli-6.28.2.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"""AWS developer tools resource collectors."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Dict, List, Any, Optional
|
|
5
|
+
|
|
6
|
+
from ..base import BaseCollector
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger("regscale")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DeveloperToolsCollector(BaseCollector):
|
|
12
|
+
"""Collector for AWS developer tools resources with filtering support."""
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
session: Any,
|
|
17
|
+
region: str,
|
|
18
|
+
account_id: Optional[str] = None,
|
|
19
|
+
tags: Optional[Dict[str, str]] = None,
|
|
20
|
+
enabled_services: Optional[Dict[str, bool]] = None,
|
|
21
|
+
):
|
|
22
|
+
"""
|
|
23
|
+
Initialize developer tools collector with filtering support.
|
|
24
|
+
|
|
25
|
+
:param session: AWS session to use for API calls
|
|
26
|
+
:param str region: AWS region to collect from
|
|
27
|
+
:param str account_id: Optional AWS account ID to filter resources
|
|
28
|
+
:param dict tags: Optional tag filters (AND logic)
|
|
29
|
+
:param dict enabled_services: Optional dict of service names to boolean flags for enabling/disabling collection
|
|
30
|
+
"""
|
|
31
|
+
super().__init__(session, region, account_id, tags)
|
|
32
|
+
self.enabled_services = enabled_services or {}
|
|
33
|
+
|
|
34
|
+
def get_codepipeline_pipelines(self) -> List[Dict[str, Any]]:
|
|
35
|
+
"""
|
|
36
|
+
Get information about CodePipeline pipelines.
|
|
37
|
+
|
|
38
|
+
:return: List of CodePipeline pipeline information
|
|
39
|
+
:rtype: List[Dict[str, Any]]
|
|
40
|
+
"""
|
|
41
|
+
pipelines = []
|
|
42
|
+
try:
|
|
43
|
+
codepipeline_client = self._get_client("codepipeline")
|
|
44
|
+
paginator = codepipeline_client.get_paginator("list_pipelines")
|
|
45
|
+
|
|
46
|
+
for page in paginator.paginate():
|
|
47
|
+
for pipeline_summary in page.get("pipelines", []):
|
|
48
|
+
pipeline_name = pipeline_summary.get("name")
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
pipeline_detail = codepipeline_client.get_pipeline(name=pipeline_name)
|
|
52
|
+
pipeline = pipeline_detail.get("pipeline", {})
|
|
53
|
+
pipeline_arn = pipeline.get("pipelineArn", "")
|
|
54
|
+
|
|
55
|
+
if not self._matches_account(pipeline_arn):
|
|
56
|
+
continue
|
|
57
|
+
|
|
58
|
+
tags_response = codepipeline_client.list_tags_for_resource(resourceArn=pipeline_arn)
|
|
59
|
+
pipeline_tags = tags_response.get("tags", [])
|
|
60
|
+
|
|
61
|
+
if not self._matches_tags(pipeline_tags):
|
|
62
|
+
continue
|
|
63
|
+
|
|
64
|
+
pipelines.append(
|
|
65
|
+
{
|
|
66
|
+
"Region": self.region,
|
|
67
|
+
"PipelineName": pipeline_name,
|
|
68
|
+
"PipelineArn": pipeline_arn,
|
|
69
|
+
"RoleArn": pipeline.get("roleArn"),
|
|
70
|
+
"Version": pipeline.get("version"),
|
|
71
|
+
"Created": pipeline_summary.get("created"),
|
|
72
|
+
"Updated": pipeline_summary.get("updated"),
|
|
73
|
+
"Tags": pipeline_tags,
|
|
74
|
+
}
|
|
75
|
+
)
|
|
76
|
+
except Exception as pipeline_error:
|
|
77
|
+
logger.debug("Error getting CodePipeline details for %s: %s", pipeline_name, pipeline_error)
|
|
78
|
+
continue
|
|
79
|
+
|
|
80
|
+
except Exception as e:
|
|
81
|
+
self._handle_error(e, "CodePipeline pipelines")
|
|
82
|
+
return pipelines
|
|
83
|
+
|
|
84
|
+
def get_codebuild_projects(self) -> List[Dict[str, Any]]:
|
|
85
|
+
"""
|
|
86
|
+
Get information about CodeBuild projects.
|
|
87
|
+
|
|
88
|
+
:return: List of CodeBuild project information
|
|
89
|
+
:rtype: List[Dict[str, Any]]
|
|
90
|
+
"""
|
|
91
|
+
projects = []
|
|
92
|
+
try:
|
|
93
|
+
codebuild_client = self._get_client("codebuild")
|
|
94
|
+
paginator = codebuild_client.get_paginator("list_projects")
|
|
95
|
+
|
|
96
|
+
for page in paginator.paginate():
|
|
97
|
+
project_names = page.get("projects", [])
|
|
98
|
+
|
|
99
|
+
if not project_names:
|
|
100
|
+
continue
|
|
101
|
+
|
|
102
|
+
batch_response = codebuild_client.batch_get_projects(names=project_names)
|
|
103
|
+
for project in batch_response.get("projects", []):
|
|
104
|
+
project_arn = project.get("arn", "")
|
|
105
|
+
|
|
106
|
+
if not self._matches_account(project_arn):
|
|
107
|
+
continue
|
|
108
|
+
|
|
109
|
+
if not self._matches_tags(project.get("tags", [])):
|
|
110
|
+
continue
|
|
111
|
+
|
|
112
|
+
projects.append(
|
|
113
|
+
{
|
|
114
|
+
"Region": self.region,
|
|
115
|
+
"ProjectName": project.get("name"),
|
|
116
|
+
"ProjectArn": project_arn,
|
|
117
|
+
"Description": project.get("description"),
|
|
118
|
+
"ServiceRole": project.get("serviceRole"),
|
|
119
|
+
"Created": project.get("created"),
|
|
120
|
+
"LastModified": project.get("lastModified"),
|
|
121
|
+
"Environment": project.get("environment"),
|
|
122
|
+
"Tags": project.get("tags", []),
|
|
123
|
+
}
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
except Exception as e:
|
|
127
|
+
self._handle_error(e, "CodeBuild projects")
|
|
128
|
+
return projects
|
|
129
|
+
|
|
130
|
+
def get_codedeploy_applications(self) -> List[Dict[str, Any]]:
|
|
131
|
+
"""
|
|
132
|
+
Get information about CodeDeploy applications.
|
|
133
|
+
|
|
134
|
+
:return: List of CodeDeploy application information
|
|
135
|
+
:rtype: List[Dict[str, Any]]
|
|
136
|
+
"""
|
|
137
|
+
applications = []
|
|
138
|
+
try:
|
|
139
|
+
codedeploy_client = self._get_client("codedeploy")
|
|
140
|
+
paginator = codedeploy_client.get_paginator("list_applications")
|
|
141
|
+
|
|
142
|
+
for page in paginator.paginate():
|
|
143
|
+
for app_name in page.get("applications", []):
|
|
144
|
+
try:
|
|
145
|
+
app_detail = codedeploy_client.get_application(applicationName=app_name)
|
|
146
|
+
application = app_detail.get("application", {})
|
|
147
|
+
app_arn = f"arn:aws:codedeploy:{self.region}:{self.account_id or '*'}:application:{app_name}"
|
|
148
|
+
|
|
149
|
+
if not self._matches_account(app_arn):
|
|
150
|
+
continue
|
|
151
|
+
|
|
152
|
+
tags_response = codedeploy_client.list_tags_for_resource(ResourceArn=app_arn)
|
|
153
|
+
app_tags = tags_response.get("Tags", [])
|
|
154
|
+
|
|
155
|
+
if not self._matches_tags(app_tags):
|
|
156
|
+
continue
|
|
157
|
+
|
|
158
|
+
applications.append(
|
|
159
|
+
{
|
|
160
|
+
"Region": self.region,
|
|
161
|
+
"ApplicationName": app_name,
|
|
162
|
+
"ApplicationId": application.get("applicationId"),
|
|
163
|
+
"CreateTime": application.get("createTime"),
|
|
164
|
+
"ComputePlatform": application.get("computePlatform"),
|
|
165
|
+
"Tags": app_tags,
|
|
166
|
+
}
|
|
167
|
+
)
|
|
168
|
+
except Exception as app_error:
|
|
169
|
+
logger.debug("Error getting CodeDeploy application details for %s: %s", app_name, app_error)
|
|
170
|
+
continue
|
|
171
|
+
|
|
172
|
+
except Exception as e:
|
|
173
|
+
self._handle_error(e, "CodeDeploy applications")
|
|
174
|
+
return applications
|
|
175
|
+
|
|
176
|
+
def get_codecommit_repositories(self) -> List[Dict[str, Any]]:
|
|
177
|
+
"""
|
|
178
|
+
Get information about CodeCommit repositories.
|
|
179
|
+
|
|
180
|
+
:return: List of CodeCommit repository information
|
|
181
|
+
:rtype: List[Dict[str, Any]]
|
|
182
|
+
"""
|
|
183
|
+
repositories = []
|
|
184
|
+
try:
|
|
185
|
+
codecommit_client = self._get_client("codecommit")
|
|
186
|
+
paginator = codecommit_client.get_paginator("list_repositories")
|
|
187
|
+
|
|
188
|
+
for page in paginator.paginate():
|
|
189
|
+
for repo_metadata in page.get("repositories", []):
|
|
190
|
+
repo_name = repo_metadata.get("repositoryName")
|
|
191
|
+
|
|
192
|
+
try:
|
|
193
|
+
repo_detail = codecommit_client.get_repository(repositoryName=repo_name)
|
|
194
|
+
repository = repo_detail.get("repositoryMetadata", {})
|
|
195
|
+
repo_arn = repository.get("Arn", "")
|
|
196
|
+
|
|
197
|
+
if not self._matches_account(repo_arn):
|
|
198
|
+
continue
|
|
199
|
+
|
|
200
|
+
tags_response = codecommit_client.list_tags_for_resource(resourceArn=repo_arn)
|
|
201
|
+
repo_tags = tags_response.get("tags", {})
|
|
202
|
+
|
|
203
|
+
if not self._matches_tags(repo_tags):
|
|
204
|
+
continue
|
|
205
|
+
|
|
206
|
+
repositories.append(
|
|
207
|
+
{
|
|
208
|
+
"Region": self.region,
|
|
209
|
+
"RepositoryName": repo_name,
|
|
210
|
+
"RepositoryId": repository.get("repositoryId"),
|
|
211
|
+
"RepositoryArn": repo_arn,
|
|
212
|
+
"DefaultBranch": repository.get("defaultBranch"),
|
|
213
|
+
"CloneUrlHttp": repository.get("cloneUrlHttp"),
|
|
214
|
+
"CloneUrlSsh": repository.get("cloneUrlSsh"),
|
|
215
|
+
"CreationDate": repository.get("creationDate"),
|
|
216
|
+
"LastModifiedDate": repository.get("lastModifiedDate"),
|
|
217
|
+
"Tags": repo_tags,
|
|
218
|
+
}
|
|
219
|
+
)
|
|
220
|
+
except Exception as repo_error:
|
|
221
|
+
logger.debug("Error getting CodeCommit repository details for %s: %s", repo_name, repo_error)
|
|
222
|
+
continue
|
|
223
|
+
|
|
224
|
+
except Exception as e:
|
|
225
|
+
self._handle_error(e, "CodeCommit repositories")
|
|
226
|
+
return repositories
|
|
227
|
+
|
|
228
|
+
def collect(self) -> Dict[str, Any]:
|
|
229
|
+
"""
|
|
230
|
+
Collect developer tools resources based on enabled_services configuration.
|
|
231
|
+
|
|
232
|
+
:return: Dictionary containing enabled developer tools resource information
|
|
233
|
+
:rtype: Dict[str, Any]
|
|
234
|
+
"""
|
|
235
|
+
result = {}
|
|
236
|
+
|
|
237
|
+
# CodePipeline
|
|
238
|
+
if self.enabled_services.get("codepipeline", True):
|
|
239
|
+
result["CodePipelinePipelines"] = self.get_codepipeline_pipelines()
|
|
240
|
+
|
|
241
|
+
# CodeBuild
|
|
242
|
+
if self.enabled_services.get("codebuild", True):
|
|
243
|
+
result["CodeBuildProjects"] = self.get_codebuild_projects()
|
|
244
|
+
|
|
245
|
+
# CodeDeploy
|
|
246
|
+
if self.enabled_services.get("codedeploy", True):
|
|
247
|
+
result["CodeDeployApplications"] = self.get_codedeploy_applications()
|
|
248
|
+
|
|
249
|
+
# CodeCommit
|
|
250
|
+
if self.enabled_services.get("codecommit", True):
|
|
251
|
+
result["CodeCommitRepositories"] = self.get_codecommit_repositories()
|
|
252
|
+
|
|
253
|
+
return result
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"""AWS GuardDuty resource collection."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Any, Dict, List, Optional
|
|
5
|
+
|
|
6
|
+
from botocore.exceptions import ClientError
|
|
7
|
+
|
|
8
|
+
from regscale.integrations.commercial.aws.inventory.base import BaseCollector
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger("regscale")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class GuardDutyCollector(BaseCollector):
|
|
14
|
+
"""Collector for AWS GuardDuty resources."""
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
session: Any,
|
|
19
|
+
region: str,
|
|
20
|
+
account_id: Optional[str] = None,
|
|
21
|
+
tags: Optional[Dict[str, str]] = None,
|
|
22
|
+
collect_findings: bool = True,
|
|
23
|
+
):
|
|
24
|
+
"""
|
|
25
|
+
Initialize GuardDuty collector.
|
|
26
|
+
|
|
27
|
+
:param session: AWS session to use for API calls
|
|
28
|
+
:param str region: AWS region to collect from
|
|
29
|
+
:param str account_id: Optional AWS account ID to filter resources
|
|
30
|
+
:param dict tags: Optional tags to filter resources (key-value pairs)
|
|
31
|
+
:param bool collect_findings: Whether to collect GuardDuty findings. Default True.
|
|
32
|
+
"""
|
|
33
|
+
super().__init__(session, region)
|
|
34
|
+
self.account_id = account_id
|
|
35
|
+
self.tags = tags or {}
|
|
36
|
+
self.collect_findings = collect_findings
|
|
37
|
+
|
|
38
|
+
def collect(self) -> Dict[str, Any]:
|
|
39
|
+
"""
|
|
40
|
+
Collect GuardDuty resources.
|
|
41
|
+
|
|
42
|
+
:return: Dictionary containing GuardDuty detectors, findings, and members
|
|
43
|
+
:rtype: Dict[str, Any]
|
|
44
|
+
"""
|
|
45
|
+
result = {"Detectors": [], "Findings": [], "Members": []}
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
client = self._get_client("guardduty")
|
|
49
|
+
detector_ids = self._list_detectors(client)
|
|
50
|
+
|
|
51
|
+
for detector_id in detector_ids:
|
|
52
|
+
self._process_detector(client, detector_id, result)
|
|
53
|
+
|
|
54
|
+
if self.collect_findings:
|
|
55
|
+
logger.info(
|
|
56
|
+
f"Collected {len(result['Detectors'])} GuardDuty detector(s), "
|
|
57
|
+
f"{len(result['Findings'])} finding(s) from {self.region}"
|
|
58
|
+
)
|
|
59
|
+
else:
|
|
60
|
+
logger.info(f"Collected {len(result['Detectors'])} GuardDuty detector(s) from {self.region}")
|
|
61
|
+
|
|
62
|
+
except ClientError as e:
|
|
63
|
+
self._handle_error(e, "GuardDuty resources")
|
|
64
|
+
except Exception as e:
|
|
65
|
+
logger.error(f"Unexpected error collecting GuardDuty resources: {e}", exc_info=True)
|
|
66
|
+
|
|
67
|
+
return result
|
|
68
|
+
|
|
69
|
+
def _process_detector(self, client: Any, detector_id: str, result: Dict[str, Any]) -> None:
|
|
70
|
+
"""
|
|
71
|
+
Process a single detector and add its details to result.
|
|
72
|
+
|
|
73
|
+
:param client: GuardDuty client
|
|
74
|
+
:param str detector_id: Detector ID to process
|
|
75
|
+
:param dict result: Result dictionary to populate
|
|
76
|
+
"""
|
|
77
|
+
detector_info = self._get_detector(client, detector_id)
|
|
78
|
+
if not detector_info:
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
if not self._should_include_detector(detector_info, detector_id):
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
detector_info = self._enrich_detector_info(client, detector_id, detector_info)
|
|
85
|
+
result["Detectors"].append(detector_info)
|
|
86
|
+
|
|
87
|
+
if self.collect_findings:
|
|
88
|
+
findings = self._list_and_get_findings(client, detector_id)
|
|
89
|
+
result["Findings"].extend(findings)
|
|
90
|
+
else:
|
|
91
|
+
logger.debug(f"Skipping GuardDuty findings collection for detector {detector_id} (collect_findings=False)")
|
|
92
|
+
|
|
93
|
+
members = self._list_members(client, detector_id)
|
|
94
|
+
result["Members"].extend(members)
|
|
95
|
+
|
|
96
|
+
def _should_include_detector(self, detector_info: Dict[str, Any], detector_id: str) -> bool:
|
|
97
|
+
"""
|
|
98
|
+
Check if detector should be included based on filters.
|
|
99
|
+
|
|
100
|
+
:param dict detector_info: Detector information
|
|
101
|
+
:param str detector_id: Detector ID
|
|
102
|
+
:return: True if detector should be included
|
|
103
|
+
:rtype: bool
|
|
104
|
+
"""
|
|
105
|
+
if self.account_id and not self._matches_account_id(detector_info.get("AccountId", "")):
|
|
106
|
+
logger.debug(f"Skipping detector {detector_id} - does not match account ID {self.account_id}")
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
if self.tags:
|
|
110
|
+
detector_tags = self._get_detector_tags(
|
|
111
|
+
self._get_client("guardduty"), detector_id, detector_info.get("AccountId", "")
|
|
112
|
+
)
|
|
113
|
+
if not self._matches_tags(detector_tags):
|
|
114
|
+
logger.debug(f"Skipping detector {detector_id} - does not match tag filters")
|
|
115
|
+
return False
|
|
116
|
+
|
|
117
|
+
return True
|
|
118
|
+
|
|
119
|
+
def _enrich_detector_info(self, client: Any, detector_id: str, detector_info: Dict[str, Any]) -> Dict[str, Any]:
|
|
120
|
+
"""
|
|
121
|
+
Enrich detector info with additional metadata.
|
|
122
|
+
|
|
123
|
+
:param client: GuardDuty client
|
|
124
|
+
:param str detector_id: Detector ID
|
|
125
|
+
:param dict detector_info: Detector information to enrich
|
|
126
|
+
:return: Enriched detector information
|
|
127
|
+
:rtype: Dict[str, Any]
|
|
128
|
+
"""
|
|
129
|
+
if self.tags:
|
|
130
|
+
detector_tags = self._get_detector_tags(client, detector_id, detector_info.get("AccountId", ""))
|
|
131
|
+
detector_info["Tags"] = detector_tags
|
|
132
|
+
|
|
133
|
+
detector_info["DetectorId"] = detector_id
|
|
134
|
+
detector_info["Region"] = self.region
|
|
135
|
+
return detector_info
|
|
136
|
+
|
|
137
|
+
def _list_detectors(self, client: Any) -> List[str]:
|
|
138
|
+
"""
|
|
139
|
+
List GuardDuty detectors.
|
|
140
|
+
|
|
141
|
+
:param client: GuardDuty client
|
|
142
|
+
:return: List of detector IDs
|
|
143
|
+
:rtype: List[str]
|
|
144
|
+
"""
|
|
145
|
+
try:
|
|
146
|
+
response = client.list_detectors()
|
|
147
|
+
return response.get("DetectorIds", [])
|
|
148
|
+
except ClientError as e:
|
|
149
|
+
if e.response["Error"]["Code"] == "AccessDeniedException":
|
|
150
|
+
logger.warning(f"Access denied to list GuardDuty detectors in {self.region}")
|
|
151
|
+
return []
|
|
152
|
+
raise
|
|
153
|
+
|
|
154
|
+
def _get_detector(self, client: Any, detector_id: str) -> Optional[Dict[str, Any]]:
|
|
155
|
+
"""
|
|
156
|
+
Get details about a specific GuardDuty detector.
|
|
157
|
+
|
|
158
|
+
:param client: GuardDuty client
|
|
159
|
+
:param str detector_id: Detector ID
|
|
160
|
+
:return: Detector details or None
|
|
161
|
+
:rtype: Optional[Dict[str, Any]]
|
|
162
|
+
"""
|
|
163
|
+
try:
|
|
164
|
+
response = client.get_detector(DetectorId=detector_id)
|
|
165
|
+
# Remove ResponseMetadata
|
|
166
|
+
response.pop("ResponseMetadata", None)
|
|
167
|
+
return response
|
|
168
|
+
except ClientError as e:
|
|
169
|
+
logger.error(f"Error getting detector {detector_id}: {e}")
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
def _list_and_get_findings(self, client: Any, detector_id: str, max_findings: int = 50) -> List[Dict[str, Any]]:
|
|
173
|
+
"""
|
|
174
|
+
List and get detailed information about GuardDuty findings.
|
|
175
|
+
|
|
176
|
+
:param client: GuardDuty client
|
|
177
|
+
:param str detector_id: Detector ID
|
|
178
|
+
:param int max_findings: Maximum number of findings to retrieve
|
|
179
|
+
:return: List of findings with details
|
|
180
|
+
:rtype: List[Dict[str, Any]]
|
|
181
|
+
"""
|
|
182
|
+
findings = []
|
|
183
|
+
|
|
184
|
+
try:
|
|
185
|
+
# List finding IDs
|
|
186
|
+
list_response = client.list_findings(DetectorId=detector_id, MaxResults=max_findings)
|
|
187
|
+
finding_ids = list_response.get("FindingIds", [])
|
|
188
|
+
|
|
189
|
+
if not finding_ids:
|
|
190
|
+
return findings
|
|
191
|
+
|
|
192
|
+
# Get detailed information for findings
|
|
193
|
+
get_response = client.get_findings(DetectorId=detector_id, FindingIds=finding_ids)
|
|
194
|
+
findings = get_response.get("Findings", [])
|
|
195
|
+
|
|
196
|
+
# Add region information
|
|
197
|
+
for finding in findings:
|
|
198
|
+
finding["Region"] = self.region
|
|
199
|
+
finding["DetectorId"] = detector_id
|
|
200
|
+
|
|
201
|
+
except ClientError as e:
|
|
202
|
+
if e.response["Error"]["Code"] == "AccessDeniedException":
|
|
203
|
+
logger.warning(f"Access denied to list findings for detector {detector_id}")
|
|
204
|
+
else:
|
|
205
|
+
logger.error(f"Error listing findings for detector {detector_id}: {e}")
|
|
206
|
+
|
|
207
|
+
return findings
|
|
208
|
+
|
|
209
|
+
def _list_members(self, client: Any, detector_id: str) -> List[Dict[str, Any]]:
|
|
210
|
+
"""
|
|
211
|
+
List member accounts for a GuardDuty detector.
|
|
212
|
+
|
|
213
|
+
:param client: GuardDuty client
|
|
214
|
+
:param str detector_id: Detector ID
|
|
215
|
+
:return: List of member accounts
|
|
216
|
+
:rtype: List[Dict[str, Any]]
|
|
217
|
+
"""
|
|
218
|
+
members = []
|
|
219
|
+
|
|
220
|
+
try:
|
|
221
|
+
response = client.list_members(DetectorId=detector_id)
|
|
222
|
+
members = response.get("Members", [])
|
|
223
|
+
|
|
224
|
+
# Add region and detector information
|
|
225
|
+
for member in members:
|
|
226
|
+
member["Region"] = self.region
|
|
227
|
+
member["DetectorId"] = detector_id
|
|
228
|
+
|
|
229
|
+
except ClientError as e:
|
|
230
|
+
if e.response["Error"]["Code"] == "AccessDeniedException":
|
|
231
|
+
logger.debug(f"Access denied to list members for detector {detector_id}")
|
|
232
|
+
else:
|
|
233
|
+
logger.error(f"Error listing members for detector {detector_id}: {e}")
|
|
234
|
+
|
|
235
|
+
return members
|
|
236
|
+
|
|
237
|
+
def _matches_account_id(self, detector_account_id: str) -> bool:
|
|
238
|
+
"""
|
|
239
|
+
Check if detector account ID matches the specified account ID.
|
|
240
|
+
|
|
241
|
+
:param str detector_account_id: Account ID from detector
|
|
242
|
+
:return: True if matches or no account_id filter specified
|
|
243
|
+
:rtype: bool
|
|
244
|
+
"""
|
|
245
|
+
if not self.account_id:
|
|
246
|
+
return True
|
|
247
|
+
|
|
248
|
+
return detector_account_id == self.account_id
|
|
249
|
+
|
|
250
|
+
def _get_detector_tags(self, client: Any, detector_id: str, account_id: str) -> Dict[str, str]:
|
|
251
|
+
"""
|
|
252
|
+
Get tags for a GuardDuty detector.
|
|
253
|
+
|
|
254
|
+
:param client: GuardDuty client
|
|
255
|
+
:param str detector_id: Detector ID
|
|
256
|
+
:param str account_id: AWS account ID
|
|
257
|
+
:return: Dictionary of tags (TagKey -> TagValue)
|
|
258
|
+
:rtype: Dict[str, str]
|
|
259
|
+
"""
|
|
260
|
+
try:
|
|
261
|
+
# Construct the detector ARN
|
|
262
|
+
detector_arn = f"arn:aws:guardduty:{self.region}:{account_id}:detector/{detector_id}"
|
|
263
|
+
response = client.list_tags_for_resource(ResourceArn=detector_arn)
|
|
264
|
+
tags = response.get("Tags", {})
|
|
265
|
+
return tags
|
|
266
|
+
except ClientError as e:
|
|
267
|
+
logger.debug(f"Error getting tags for detector {detector_id}: {e}")
|
|
268
|
+
return {}
|
|
269
|
+
|
|
270
|
+
def _matches_tags(self, resource_tags: Dict[str, str]) -> bool:
|
|
271
|
+
"""
|
|
272
|
+
Check if resource tags match the specified filter tags.
|
|
273
|
+
|
|
274
|
+
:param dict resource_tags: Tags on the resource
|
|
275
|
+
:return: True if all filter tags match
|
|
276
|
+
:rtype: bool
|
|
277
|
+
"""
|
|
278
|
+
if not self.tags:
|
|
279
|
+
return True
|
|
280
|
+
|
|
281
|
+
# All filter tags must match
|
|
282
|
+
for key, value in self.tags.items():
|
|
283
|
+
if resource_tags.get(key) != value:
|
|
284
|
+
return False
|
|
285
|
+
|
|
286
|
+
return True
|